Ожидание объекта 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;
fst := TFileScannerThread.Create(BaseFolder, ScanMode, AbortEvent);
...
WaitForSingleObject(fst.Handle, INFINITE);
fst.Free;
(вам не нужно звонить Start()
внутри конструктора, используйте CreateSuspended=False
, вместо этого поток фактически не начнет работать, пока не выйдет конструктор)
При этом WaitForSingleObject()
невозможно вернуть WAIT_TIMEOUT
в INFINITE
тайм-аут. Но он может вернуть WAIT_FAILED
, например, когда объект TThread
освобожден до вызова WaitForSingleObject()
, или даже когда он все еще ожидает на Handle
, таким образом закрывая Handle
, пока он
Что касается тупика, если у вас есть OnTerminate
обработчик событий, назначенный объекту TThread
, этот обработчик вызывается с помощью метода TThread::Synchronize()
, так что обработчик запускается в Основная тема. Но если основной поток заблокирован на WaitForSingleObject()
, он не сможет обслуживать запросы TThread::Synchronize()
(или TThread::Queue()
). Таким образом, в итоге рабочий поток ожидает в основном потоке, в то время как основной поток ожидает в рабочем потоке - тупик.
Чтобы избежать этого, вы можете вызвать WaitForSingleObject()
в al oop с помощью короткий тайм-аут, так что вы можете периодически вызывать функцию RTL CheckSynchronize()
во время ожидания:
var h: THandle;
h := fst.Handle;
while WaitForSingleObject(h, 500) = WAIT_TIMEOUT do
CheckSynchronize;
fst.Free;
При использовании блокирующего ожидания возникают другие проблемы, такие как 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;
В качестве альтернативы, добавьте дескриптор 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;
К вашему сведению, TThread
имеет свой собственный метод WaitFor()
, который выполняет ожидание блокировки потока для завершения при обслуживании TThread::Synchronize()
/ TThread::Queue()
и SendMessage()
запросов, аналогичных приведенным выше:
fst := TFileScannerThread.Create(BaseFolder, ScanMode, AbortEvent);
...
fst.WaitFor;
fst.Free;
Просто обратите внимание, что вызов TThread::WaitFor()
для TThread
объекта, который использует FreeOnTerminate=true
, также небезопасен. Он либо завершится с ошибкой EThread
или EOSError
, когда Handle
будет закрыт между внутренними итерациями l oop, либо, скорее всего, просто взломает sh сразу, когда попытается получить доступ к Handle
TThread
объект, который уже был освобожден.