【发布时间】:2020-07-31 07:20:00
【问题描述】:
我有一个任务要在后台运行,而不是中断 GUI 线程来检查新的程序版本。
当应用程序启动时,它立即将一个线程排队等待 15 秒,然后执行剩余的代码。如果检查是在 15 秒结束之前手动触发的,则应该终止现有的自动线程。如果应用程序在任何时候关闭,任何剩余的线程都应尽快终止。
为了便于调试,我在下面的示例中使用了 2 分钟。
我现在的问题是,我尝试使用 WaitFor、Terminate、FreeOnTerminate 和 OnTerminated 的任何组合都不会获得预期的结果。要么 Destroy 没有被调用,我得到内存泄漏,应用程序在终止线程时挂起,或者我得到 Cannot terminate externally created thread 异常。
线程代码
unit unCheckThread;
interface
uses SysUtils, Classes, SyncObjs, Dialogs;
type
TCheckThread = class(TThread)
private
FDelayEvent: TEvent;
FDelay: Integer;
public
constructor Create(const ADelay: Integer);
destructor Destroy; override;
procedure Execute; override;
procedure TerminatedSet; override;
end;
implementation
{ TCheckThread }
constructor TCheckThread.Create(const ADelay: Integer);
begin
inherited Create(True);
FreeOnTerminate := False;
FDelay := ADelay;
FDelayEvent := TEvent.Create(nil, True, False, '');
end;
destructor TCheckThread.Destroy;
begin
FDelayEvent.Free;
inherited;
end;
procedure TCheckThread.Execute;
begin
FDelayEvent.WaitFor(MSecsPerSec * FDelay);
{ if another thread has checked while waiting for the delay, cancel this check }
if Terminated then Exit;
{ some long running code }
Sleep(10000);
if Terminated then Exit;
Synchronize(
procedure()
begin
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
end);
end;
procedure TCheckThread.TerminatedSet;
begin
FDelayEvent.SetEvent;
end;
end.
用户界面
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, unCheckThread, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure onterminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ?
end;
AThread := TCheckThread.Create(0); // start immediately
AThread.OnTerminate := onterminate;
AThread.Start;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ??
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AThread := TCheckThread.Create(SecsPerMin * 2); // wait for 2 mintues before starting
end;
procedure TForm1.onterminate(Sender: TObject);
begin
FreeAndNil(AThread);
end;
end.
【问题讨论】:
-
1. 使用
FormDestroy而不是FormClose。 2. 删除OnTerminate事件处理程序。 3. 在FormDestroy和Button1Click中都使用FreeAndNil(AThread)而不是请求终止和等待线程。从主线程中释放线程将为您调用适当的例程。 4. 不要让胎面休眠。它将无法响应取消。您已经使用手动重置事件作为取消初始延迟的信号。稍后再做同样的事情 - 定期检查事件的状态。 -
@PeterWolf "而不是请求终止并等待线程使用
FreeAndNil(AThread)" - 这是非常糟糕的建议。Free运行TThread是不安全的。在FreeTThread之前,始终明确 请求终止并等待终止完成。这是安全的方式。 -
@RemyLebeau 感谢您的调整。我知道
Free'ing 正在运行的线程可能会导致一些问题。另一方面,Destroy比Terminate和WaitFor线程做得更多。它还有Resume的暂停线程和Yield直到它启动,如果它没有启动。因此Terminate&WaitFor在所有情况下都不够用。我有兴趣进一步阅读如何在 Delphi 中优雅地关闭线程以及什么是陷阱。最值得注意的是,它在调用inherited之前在重写的析构函数中破坏了线程的字段,我会说。
标签: delphi delphi-xe2