Освободите TThread автоматически или вручную
У меня есть основной поток и отдельный поток в моей программе. Если отдельный поток заканчивается перед основным потоком, он должен автоматически освободиться. Если основной поток заканчивается первым, он должен освободить отдельный поток.
Я знаю о FreeOnTerminate, и я прочитал, что вы должны быть осторожны, используя его.
У меня вопрос, правильный ли следующий код?
procedure TMyThread.Execute;
begin
... Do some processing
Synchronize(ThreadFinished);
if Terminated then exit;
FreeOnTerminate := true;
end;
procedure TMyThread.ThreadFinished;
begin
MainForm.MyThreadReady := true;
end;
procedure TMainForm.Create;
begin
MyThreadReady := false;
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
5 ответов
Вы можете упростить это до:
procedure TMyThread.Execute;
begin
// ... Do some processing
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(false);
end;
procedure TMainForm.Close;
begin
if Assigned(MyThread) then
MyThread.Terminate;
MyThread.Free;
end;
Объяснение:
Либо использовать
FreeOnTerminate
или освободите поток вручную, но никогда не делайте и то и другое. Асинхронный характер выполнения потока означает, что вы рискуете не освободить поток или (что еще хуже) сделать это дважды. Нет никакого риска держать объект потока после того, как он завершил выполнение, и нет никакого риска в вызовеTerminate()
на нить, которая уже закончила либо.Нет необходимости синхронизировать доступ к логическому значению, которое записывается только из одного потока и читается из другого. В худшем случае вы получаете неправильное значение, но из-за асинхронного выполнения, которое в любом случае является ложным эффектом. Синхронизация необходима только для данных, которые не могут быть прочитаны или записаны атомарно. И если вам нужно синхронизировать, не используйте
Synchronize()
для этого.Нет необходимости иметь переменную, аналогичную
MyThreadReady
, как вы можете использоватьWaitForSingleObject()
опросить состояние потока. ПроходитьMyThread.Handle
как первый и0
в качестве второго параметра и проверьте, является ли результатWAIT_OBJECT_0
- если это так, ваш поток завершил выполнение.
Кстати: не используйте OnClose
событие, использовать OnDestroy
вместо. Первый не обязательно вызывается, и в этом случае ваш поток может продолжать работать и поддерживать ваш процесс живым.
Пусть основной поток назначит обработчик событию OnTerminate рабочего потока. Если рабочий поток завершается первым, то обработчик может дать сигнал основному потоку об освобождении потока. Если основной поток завершается первым, он может завершить рабочий поток. Например:
procedure TMyThread.Execute;
begin
... Do some processing ...
end;
procedure TMainForm.Create;
begin
MyThread := TMyThread.Create(True);
MyThread.OnTerminate := ThreadFinished;
MyThread.Resume; // or MyThread.Start; in D2010+
end;
const
APPWM_FREE_THREAD = WM_APP+1;
procedure TMainForm.ThreadFinished(Sender: TObject);
begin
PostMessage(Handle, APPWM_FREE_THREAD, 0, 0);
end;
procedure TMainForm.WndProc(var Message: TMessage);
begin
if Message.Msg = APPWM_FREE_THREAD then
StopWorkerThread
else
inherited;
end;
procedure TMainForm.StopWorkerThread;
begin
if MyThread <> nil then
begin
MyThread.Terminate;
MyThread.WaitFor;
FreeAndNil(MyThread);
end;
end;
procedure TMainForm.Close;
begin
StopWorkerThread;
end;
Нет, ваш код не очень хорош (хотя, вероятно, он будет работать в 99,99% или даже в 100% случаев). Если вы планируете прервать рабочий поток из основного потока, не устанавливайте FreeOnTerminate в True (я не вижу, что вы пытаетесь получить в приведенном выше коде, установив FreeOnTerminate в True, по крайней мере это делает ваш код менее понятным),
Более важной ситуацией с завершением рабочих потоков является то, что вы пытаетесь закрыть приложение, когда рабочий поток находится в состоянии ожидания. Поток не будет пробужден, если вы просто вызовете Terminate, обычно вы должны использовать дополнительный объект синхронизации (обычно событие) для пробуждения рабочего потока.
И еще одно замечание - нет необходимости
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
если вы посмотрите на код TThread.Destroy, он вызывает Terminate и WaitFor, поэтому
MyThread.Free;
достаточно (по крайней мере, в Delphi 2009, нет источников Delphi 7 для проверки).
обновленный
Прочитайте MGIE ответ. Рассмотрим следующую ситуацию (лучше для системы с 1 процессором):
основной поток выполняется
procedure TMainForm.Close;
begin
if not MyThreadReady then
begin
MyThread.Terminate;
MyThread.WaitFor;
MyThread.Free;
end;
end;
он проверял значение MyThreadReady (это False) и был отключен планировщиком.
Теперь планировщик переключается на рабочий поток; он выполняет
Synchronize(ThreadFinished);
и заставляет планировщик переключиться обратно в основной поток. Основной поток продолжает выполнение:
MyThread.Terminate; // no problem
MyThread.WaitFor; // ???
MyThread.Free;
Можете ли вы сказать, что произойдет на WaitFor? Я не могу (требует более глубокого изучения источников TThread, чтобы ответить, но на первый взгляд выглядит как тупик).
Ваша настоящая ошибка в чем-то другом - вы написали ненадежный код и пытаетесь выяснить, верный он или нет. Это плохая практика с потоками - вместо этого вы должны научиться писать надежный код.
Что касается ресурсов - когда TThread (с FreeOnTerminate = False) завершается, единственными ресурсами, которые остаются выделенными, является дескриптор потока Windows (он не использует существенных ресурсов Windows после завершения потока) и объект Delphi TThread в памяти. Не большая цена, чтобы быть на безопасной стороне.
Честно говоря, ваш
... Do some processing
Это настоящая проблема здесь. Это цикл для чего-то рекурсивного? Если нет, и вместо этого это огромная задача, вам следует рассмотреть возможность разбить эту задачу на небольшие процедуры / функции и собрать все вместе в теле выполнения, вызывая один за другим с условными if, чтобы узнать состояние потока, например:
While not Terminated do
begin
if MyThreadReady then
DoStepOneToTaskCompletion
else
clean_and_or_rollback(Something Initialized?);
if MyThreadReady then
DoStepTwoToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne);
if MyThreadReady then
DoStepThreeToTaskCompletion
else
clean_and_or_rollback(Something Initialized?, StepOne, StepTwo);
Self.DoTerminate; // Not sure what to expect from that one
end;
Это грязно, почти взломать, но будет работать, как ожидалось.
Про FreeOnTerminate, ну... просто удалите объявление и всегда
FreeAndNil(ThreadObject);
Я не фанат синхронизма. Мне нравятся более важные разделы, для гибкости расширения кода для обработки большего количества общих данных.
На форме публичного раздела, объявите:
ControlSection : TRTLCriticalSection;
На форме создать или где-то еще до thread.create,
InitializeCriticalSection(ControlSection);
Затем каждый раз, когда вы пишете в общий ресурс (включая переменную MyThreadReady), выполняйте
EnterCriticalSection ( ControlSection );
MyThreadReady := True; //or false, or whatever else
LeaveCriticalSection ( ControlSection );
Прежде чем идти (выход), позвоните
DeleteCriticalSection ( ControlSection );
и освободите свою ветку, как всегда.
С уважением Рафаэль
Я бы сказал, что смешивать модели просто не рекомендуется. Вы либо используете FreeOnTerminate и никогда больше не трогаете поток, либо нет. В противном случае вам нужен защищенный способ общения.
Поскольку вам нужен точный контроль над переменной потока, не используйте FreeOnTerminate. Если ваш поток завершает работу раньше времени, очистите локальные ресурсы, которые он использовал, как обычно, а затем просто дайте основному потоку освободить дочерний поток, когда приложение будет завершено. Вы получите лучшее из обоих миров - ресурсы, освобожденные дочерним потоком, как только это будет возможно, и не беспокойтесь о синхронизации потока. (И он получил дополнительный бонус за то, что он намного проще в дизайне / коде / понимании / поддержке...)