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).
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.
Edit: Creating with FreeOnTerminate=false does not create this issue.
Waiting on a
TThread
object that usesFreeOnTerminate=true
is a race condition, as the object could be freed at any moment once itsExecute()
method has exited. So unless you can guarantee that you are callingWaitForSingleObject()
before the thread'sOnTerminate
event is fired, then you are not guaranteed to have a valid object on which to read itsHandle
property. Basically, once the thread's constructor exits, all bets are off when usingFreeOnTerminate=true
, unless you use anOnTerminate
event handler.FreeOnTerminate=true
is meant for "create and forget" kind of threads. If you need to refer to a thread for any reason, usingFreeOnTerminate=true
becomes dangerous if you are not very careful.If you are going to wait on a
TThread
object, there is no point on usingFreeOnTerminate=true
on that object, since you can just free the object yourself once the wait is complete. Doing so ensures the object remains valid until you manually free it, thus you can use itsHandle
at any time:(you don't need to call
Start()
inside the constructor, useCreateSuspended=False
instead, the thread will not actually begin running until after the constructor exits)That being said, it is impossible for
WaitForSingleObject()
to returnWAIT_TIMEOUT
on anINFINITE
timeout. But it is possible for it to returnWAIT_FAILED
, such as when theTThread
object is freed beforeWaitForSingleObject()
is called, or even while it is still waiting on theHandle
, thus closing theHandle
while it is being used.With regard to a deadlock, if you have an
OnTerminate
event handler assigned to theTThread
object, that handler gets called via theTThread::Synchronize()
method so that the handler runs in the main thread. But, if the main thread is blocked onWaitForSingleObject()
, then it can't service anyTThread::Synchronize()
(orTThread::Queue()
) requests. 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'sCheckSynchronize()
function periodically while you are waiting:There are other issues to deal with when using a blocking wait, like
SendMessage()
calls from other threads to the main thread. So you would need to service those requests as well:Alternatively, add the
Classes.SyncEvent
handle to the wait as well (TThread::Synchronize()
andTThread::Queue()
signal it internally when there are requests pending):FYI,
TThread
has its ownWaitFor()
method that performs a blocking wait on the thread to terminate, while servicingTThread::Synchronize()
/TThread::Queue()
andSendMessage()
requests, similar to above:Just note that calling
TThread::WaitFor()
on aTThread
object that usesFreeOnTerminate=true
is not safe, either. It will either fail with anEThread
orEOSError
exception when theHandle
is closed between internal loop iterations, or it will likely just crash outright when it tries to access theHandle
of aTThread
object that has already been freed.