使用 WaitForSingleObject() 等待 TThread 完成

Nan*_*ani 2 delphi multithreading tthread waitforsingleobject

我正在创建一个线程,然后说要等待它以 WFSO 调用终止(下面是简化的伪代码,显然没有人想在创建线程后立即等待它)。

constructor TFileScannerThread.Create(Parameters)
begin
/// assign parameters to private variables, and call
inherited Create(true);  //Create Suspended
FreeOnTerminate := true; 
Start; 
end;
Run Code Online (Sandbox Code Playgroud)

在主线程中

fst := TFileScannerThread.Create(BaseFolder, ScanMode, AbortEvent);

///  I presume, the next call would block the main thread until the child thread is done
///  but, it seems to create a deadlock & WFSO never returns
///  I get a WAIT_TIMEOUT if I use a finite wait time
WaitForsingleObject(fst.Handle, INFINITE);
Run Code Online (Sandbox Code Playgroud)

我做错了什么/错过了什么?如果我没有 WFSO,线程会在大约 10 秒内完成。

编辑:使用 FreeOnTerminate=false 创建不会产生此问题。

Rem*_*eau 7

等待的TThread对象,用途FreeOnTerminate=true是一种竞争状态,因为对象可以在被释放任何时候一旦其Execute()方法已退出。因此,除非您能保证在线程的事件被触发WaitForSingleObject() 之前调用OnTerminate,否则您不能保证有一个有效的对象来读取其Handle属性。基本上,一旦线程的构造函数退出FreeOnTerminate=true,除非您使用OnTerminate事件处理程序,否则在使用时所有赌注都将关闭。FreeOnTerminate=true用于“创建和忘记”类型的线程。如果您出于任何原因需要引用一个线程,FreeOnTerminate=true如果您不非常小心,使用就会变得危险。

如果您要等待一个TThread对象,则FreeOnTerminate=true在该对象上使用是没有意义的,因为一旦等待完成,您就可以自己释放该对象。这样做可以确保对象在您手动释放它之前一直有效,因此您可以随时使用它Handle:

constructor TFileScannerThread.Create(Parameters)
begin
  inherited Create(False);
  FreeOnTerminate := false;
  // assign parameters to private variables
end;
Run Code Online (Sandbox Code Playgroud)
fst := TFileScannerThread.Create(BaseFolder, ScanMode, AbortEvent);
...
WaitForSingleObject(fst.Handle, INFINITE);
fst.Free;
Run Code Online (Sandbox Code Playgroud)

(您不需要Start()在构造函数内部调用,CreateSuspended=False而是使用,直到构造函数退出后,线程才会真正开始运行)

话虽如此,超时是不可能WaitForSingleObject()返回WAIT_TIMEOUT的INFINITE。但是它有可能返回WAIT_FAILED,例如当TThread对象在WaitForSingleObject()被调用之前被释放时,或者甚至在它仍在等待 时Handle,从而关闭Handle它正在被使用的时候。

关于死锁,如果您有一个OnTerminate分配给TThread对象的事件处理程序,该处理程序将通过该TThread::Synchronize()方法调用,以便处理程序在主线程中运行。但是,如果主线程在 上被阻塞WaitForSingleObject(),则它无法为任何TThread::Synchronize()(或TThread::Queue())请求提供服务。因此,当主线程正在等待工作线程时,您最终会看到工作线程在等待主线程——死锁。

为避免这种情况,您可以WaitForSingleObject()在一个具有较短超时时间的循环中调用,以便您可以CheckSynchronize()在等待时定期调用 RTL 的函数:

var h: THandle;

h := fst.Handle;
while WaitForSingleObject(h, 500) = WAIT_TIMEOUT do
  CheckSynchronize;

fst.Free;
Run Code Online (Sandbox Code Playgroud)

使用阻塞等待时还有其他问题需要处理,例如SendMessage()从其他线程调用主线程。因此,您还需要为这些请求提供服务:

var
  h: THandle;
  ret: DWORD;
  msg: TMsg;

h := fst.Handle;
repeat
  case MsgWaitForMultipleObjects(1, h, False, 1000, QS_SENDMESSAGE) of
    WAIT_OBJECT_0, WAIT_FAILED: Break;
    WAIT_OBJECT_0 + 1: PeekMessage(msg, 0, 0, 0, PM_NOREMOVE);
    WAIT_TIMEOUT: CheckSynchronize;
  end;
until False;

fst.Free;
Run Code Online (Sandbox Code Playgroud)

或者,也将Classes.SyncEvent句柄添加到等待中(TThread::Synchronize()并TThread::Queue()在有请求未决时在内部发出信号):

var
  h: array[0..1] of THandle;
  ret: DWORD;
  msg: TMsg;

h[0] := fst.Handle;
h[1] := SyncEvent;
repeat
  case MsgWaitForMultipleObjects(2, h, False, INFINITE, QS_SENDMESSAGE) of
    WAIT_OBJECT_0, WAIT_FAILED: Break;
    WAIT_OBJECT_0 + 1: CheckSynchronize;
    WAIT_OBJECT_0 + 2: PeekMessage(msg, 0, 0, 0, PM_NOREMOVE);
  end;
until False;

fst.Free;
Run Code Online (Sandbox Code Playgroud)

仅供参考,TThread有自己的WaitFor()方法可以在线程上执行阻塞等待以终止,同时服务TThread::Synchronize()/TThread::Queue()和SendMessage()请求,类似于上面:

fst := TFileScannerThread.Create(BaseFolder, ScanMode, AbortEvent);
...
fst.WaitFor;
fst.Free;
Run Code Online (Sandbox Code Playgroud)

请注意,调用TThread::WaitFor()使用的TThread对象FreeOnTerminate=true也不安全。当内部循环迭代之间关闭时,它要么失败,EThread要么EOSError异常Handle,或者当它尝试访问已经释放Handle的TThread对象时,它可能会直接崩溃。

  • @Nani 实际线程是在 `AfterConstruction` 方法中启动的。 (2认同)
  • @Nani,您描述的行为是 Delphi 5 及更早版本中的错误,但在 Delphi 6 中已修复。 [根据文档](http://docwiki.embarcadero.com/Libraries/en/System.Classes.TThread.Create) : "*如果 CreateSuspished 为 false,则在构造函数之后立即调用 Execute。*" 线程始终在基本构造函数中创建为挂起状态,然后重写 `AfterConstruction()` 以启动线程运行,如果 `创建挂起=假`。`AfterConstruction()` 在所有构造函数退出后调用。 (2认同)