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 创建不会产生此问题。
等待的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对象时,它可能会直接崩溃。