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. 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. Basically, once the thread's constructor exits, all bets are off when using FreeOnTerminate=true
, unless you use an OnTerminate
event handler. FreeOnTerminate=true
is meant for "create and forget" kind of threads. If you need to refer to a thread for any reason, using FreeOnTerminate=true
becomes dangerous if you are not very careful.
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. Doing so ensures the object remains valid until you manually free it, thus you can use its Handle
at any time:
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)
That being said, it is impossible for WaitForSingleObject()
to return WAIT_TIMEOUT
on an INFINITE
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.
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. But, if the main thread is blocked on WaitForSingleObject()
, then it can't service any TThread::Synchronize()
(or TThread::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's CheckSynchronize()
function periodically while you are waiting:
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. 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):
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:
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. 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.