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.