在Delphi中,检查非挂起状态下使用FreeOnTerminate = True创建的TThread是否仍在执行的正确方法是什么?

3
我创建了一个类:TWorkerThread = class(TThread),在TWorkerThread构造函数中,我设置了FreeOnTerminate := True
然后我在我的TForm中添加了一个私有的Worker变量。当我为Worker实例化TWorkerThread时,我总是使用CreateSuspended := False
一旦启动线程,我只需要取消该进程(Terminate)并检查进程是否完成(该检查基于用户事件)。
要检查线程是否仍在活动状态,我执行以下操作:
if (Self.Worker <> nil) and (not Self.Worker.Finished) then
    //Still active

这似乎运行良好,但我担心在某个时刻 TWorkerThread 会变成 <> nil 但仍然是 .free (请记住,TWorkerThread 都是 FreeOnTerminate := True)。当一个 TWorkerThread "自我销毁" 后,它们总是变成 nil 吗?如果不是,我该如何处理(以避免访问冲突)?

另外,如果一个 Worker 在其所属的表单销毁时处于活动状态,我会执行以下操作:

if (Self.Worker <> nil) and (not Self.Worker.Finished) then
begin
    Self.Worker.Terminate;
    //wait here for completion
end;

如果在Terminate之后进行WaitFor会导致访问冲突(因为自我销毁)。 我应该在这里强制等待吗? 最好的方法是什么?

这是我在Delphi中第一次正式使用TThread。 上面的代码是我为了清晰起见精简版。


1
一旦您启动了一个自由终止线程,由于竞争条件,您不能从线程外部调用任何方法或引用任何字段。相反,您需要让线程在终止时通知您。 - David Heffernan
如果您需要检查线程状态或需要终止线程或类似操作,则不能使用自毁线程。没有其他可行的选择。 - Dalija Prasnikar
@DavidHeffernan 这并不完全正确。只要1.线程有一个OnTerminate事件处理程序分配和2. OnTerminate事件处理程序nil引用到线程,你仍然可以从主线程使用线程对象。 OnTerminate事件始终在主线程的上下文中执行,因此只要线程引用不为nil,使用线程引用将是安全的。但需要小心调用线程的哪些函数以避免死锁。 - Ken Bourassa
1
@KenBourassa,你所说的并没有改变David所说的内容。 "您需要让线程在终止时通知您...使用OnTerminate来通知" 请注意,然而,线程可能会重写TThread.DoTerminate()以触发OnTerminate 而不使用 TThread.Synchronize()(我有一些线程就是这样做的),从而重新引入您所说的如果使用OnTerminate就不会发生的线程引用竞争条件。但这是更高级的用法,大多数人不太可能经常这样做,在这种情况下,您所说的方法将正常工作。 - Remy Lebeau
@RemyLebeau 我从未挑战David的观点中的那一部分。我质疑的是如果线程是FreeOnTerminate,你不能使用线程引用的想法,这是错误的。至于你的第二个观点,是的,我猜你可以重写TThread.DoTerminate()来调用OnTerminate而不使用TThread.Synchronize()。考虑到TThread.OnTerminate明确记录为在主线程上下文中执行,我们现在谈论的不再是TThread,而是TYourThread。但由于它打破了TThread引入的“契约”,所以我会引入一个新的事件来代替它。 - Ken Bourassa
1个回答

3
在Delphi中,正确的方法是如何检查一个非挂起创建的、带有FreeOnTerminate = True的TThread是否仍在执行?
没有。
一旦启动自销毁线程,你就不能再从外部代码触碰持有该线程的引用,因为该引用随时可能变得陈旧。
在某些情况下(如果处理OnTerminate调用的逻辑在您的自定义线程类中未被修改),您可以在OnTerminate事件处理程序中安全地访问线程引用。但这并不能帮助您解决问题,因为您无法等待自销毁线程。在Windows上,这样的代码会导致异常,可以被捕获和处理,但在其他平台上,它会导致未定义的行为和随机死锁。但即使在Windows上编写这样的代码也是不可取的。
如果需要检查线程的状态、取消或等待它,则不能使用自销毁线程。一段时间内(对于可能反对前面的声明的人,是的,在某些情况下,这些事情是可能的,但它们通常会导致自伤)。
您可以使用以下工作流程来使用TWorkerThread,只要在线程构造函数中将FreeOnTerminate保留为False,并构造未挂起的线程。所有以下方法必须从主线程调用,以避免在Worker引用上发生竞争条件。在您的窗体打开时有一个额外的线程什么都不做,不会给您带来任何麻烦。
procedure TMyForm.StartWorker;
begin
  FreeAndNil(Worker); // or if Assigned(Worker) then Exit;
  Worker := TWorkerThread.Create;
end;

procedure TMyForm.CancelWorker;
begin
  // this will initiate thread termination and waiting
  FreeAndNil(Worker);
end;

procedure TMyForm.Destroy;
begin
  // technically we could only use Free here, 
  // but since other code requires niling the reference this is more consistent 
  FreeAndNil(Worker);
  inherited;
end;

Dalija Prasnikar,@RemyLebeau 感谢您的评论和回答,我停止使用自毁线程并自行管理它。如果您有兴趣,我在此处进行了代码审查:https://codereview.stackexchange.com/questions/273776/am-i-correctly-managing-the-life-cycle-create-free-cancel-of-my-tthread-in-del - AlexV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接