简体   繁体   English

使用 WaitForSingleObject() 等待 TThread 完成

[英]Waiting for a TThread to finish with WaitForSingleObject()

I am creating a thread, and then say want to wait for it to terminate with a WFSO call (simplified pseudo-code below, obviously no one wants to wait on a thread right after creating it).我正在创建一个线程,然后说要等待它通过 WFSO 调用终止(下面是简化的伪代码,显然没有人想在创建线程后立即等待它)。

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

In the main thread在主线程中

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);

What am I doing wrong/missing?我在做什么错/错过了什么? If I do not have the WFSO the thread completes in about 10 seconds.如果我没有 WFSO,线程将在大约 10 秒内完成。

Edit: Creating with FreeOnTerminate=false does not create this issue.编辑:使用 FreeOnTerminate=false 创建不会产生此问题。

Waiting on a TThread object that uses FreeOnTerminate=true is a race condition, as the object could be freed at any moment once its Execute() method has exited.在使用FreeOnTerminate=trueTThread object 上等待是一种竞争条件,因为一旦它的Execute()方法退出,object 可以随时被释放。 So unless you can guarantee that you are calling WaitForSingleObject() before the thread's OnTerminate event is fired, then you are not guaranteed to have a valid object on which to read its Handle property.因此,除非您可以保证在触发线程的OnTerminate事件之前调用WaitForSingleObject() ,否则不能保证您有一个有效的 object 可以读取其Handle属性。 Basically, once the thread's constructor exits, all bets are off when using FreeOnTerminate=true , unless you use an OnTerminate event handler.基本上,一旦线程的构造函数退出,使用FreeOnTerminate=true时所有的赌注都将关闭,除非您使用OnTerminate事件处理程序。 FreeOnTerminate=true is meant for "create and forget" kind of threads. FreeOnTerminate=true用于“创建并忘记”类型的线程。 If you need to refer to a thread for any reason, using FreeOnTerminate=true becomes dangerous if you are not very careful.如果您出于任何原因需要引用线程,如果您不小心,使用FreeOnTerminate=true会变得很危险。

If you are going to wait on a TThread object, there is no point on using FreeOnTerminate=true on that object, since you can just free the object yourself once the wait is complete.如果您要在TThread object 上等待,则在该 object 上使用FreeOnTerminate=true是没有意义的,因为您可以在等待完成后自己释放 object。 Doing so ensures the object remains valid until you manually free it, thus you can use its Handle at any time:这样做可以确保 object 在您手动释放它之前保持有效,因此您可以随时使用它的Handle

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

(you don't need to call Start() inside the constructor, use CreateSuspended=False instead, the thread will not actually begin running until after the constructor exits) (您不需要在构造函数中调用Start() ,而是使用CreateSuspended=False ,直到构造函数退出后,线程才会真正开始运行)

That being said, it is impossible for WaitForSingleObject() to return WAIT_TIMEOUT on an INFINITE timeout.话虽如此, WaitForSingleObject()不可能在INFINITE超时时返回WAIT_TIMEOUT But it is possible for it to return WAIT_FAILED , such as when the TThread object is freed before WaitForSingleObject() is called, or even while it is still waiting on the Handle , thus closing the Handle while it is being used.但是它有可能返回WAIT_FAILED ,例如在调用WaitForSingleObject()之前释放TThread object 时,或者即使它仍在等待Handle ,因此在使用Handle时关闭它。

With regard to a deadlock, if you have an OnTerminate event handler assigned to the TThread object, that handler gets called via the TThread::Synchronize() method so that the handler runs in the main thread.关于死锁,如果您将OnTerminate事件处理程序分配给TThread object,则该处理程序将通过TThread::Synchronize()方法调用,以便处理程序在主线程中运行。 But, if the main thread is blocked on WaitForSingleObject() , then it can't service any TThread::Synchronize() (or TThread::Queue() ) requests.但是,如果主线程在WaitForSingleObject()上被阻塞,那么它不能为任何TThread::Synchronize() (或TThread::Queue() )请求提供服务。 Thus, you end up with the worker thread waiting on the main thread while the main thread is waiting on the worker thread - deadlock.因此,您最终会导致工作线程在主线程上等待而主线程在工作线程上等待 - 死锁。

To avoid that, you can call WaitForSingleObject() in a loop with a short timeout so that you can call the RTL's CheckSynchronize() function periodically while you are waiting:为避免这种情况,您可以在循环中调用WaitForSingleObject()并设置一个短暂的超时,以便在等待时定期调用 RTL 的CheckSynchronize() function:

var h: THandle;

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

fst.Free;

There are other issues to deal with when using a blocking wait, like SendMessage() calls from other threads to the main thread.使用阻塞等待时还有其他问题需要处理,例如从其他线程到主线程的SendMessage()调用。 So you would need to service those requests as well:因此,您还需要为这些请求提供服务:

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;

Alternatively, add the Classes.SyncEvent handle to the wait as well ( TThread::Synchronize() and TThread::Queue() signal it internally when there are requests pending):或者,也将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;

FYI, TThread has its own WaitFor() method that performs a blocking wait on the thread to terminate, while servicing TThread::Synchronize() / TThread::Queue() and SendMessage() requests, similar to above:仅供参考, TThread有自己的WaitFor()方法,该方法在线程上执行阻塞等待以终止,同时为TThread::Synchronize() / TThread::Queue()SendMessage()请求提供服务,类似于上面:

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

Just note that calling TThread::WaitFor() on a TThread object that uses FreeOnTerminate=true is not safe, either.请注意,在使用FreeOnTerminate=trueTThread object 上调用TThread::WaitFor()也不安全。 It will either fail with an EThread or EOSError exception when the Handle is closed between internal loop iterations, or it will likely just crash outright when it tries to access the Handle of a TThread object that has already been freed.Handle在内部循环迭代之间关闭时,它将失败并出现EThreadEOSError异常,或者当它尝试访问已释放的TThread object 的Handle时,它可能会直接崩溃。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM