Освободите TThread автоматически или вручную - PullRequest
9 голосов
/ 24 августа 2010

У меня есть основной поток и отдельный поток в моей программе.Если отдельный поток заканчивается перед основным потоком, он должен автоматически освободиться.Если основной поток завершается первым, он должен освободить отдельный поток.

Я знаю о 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 ]

8 голосов
/ 24 августа 2010

Вы можете упростить это до:

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 - если это так, ваш поток завершил выполнение.

BTW:Не используйте событие OnClose, используйте вместо него OnDestroy.Первый не обязательно вызывается, и в этом случае ваш поток может продолжать работать и поддерживать ваш процесс.

4 голосов
/ 24 августа 2010

Пусть основной поток назначит обработчик событию 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;
2 голосов
/ 24 августа 2010

Нет, ваш код не очень хороший (хотя, вероятно, он будет работать в 99,99% или даже в 100% случаев).Если вы планируете завершить рабочий поток из основного потока, не устанавливайте FreeOnTerminate в True (я не вижу, что вы пытаетесь получить в приведенном выше коде, установив FreeOnTerminate в True, по крайней мере это делает ваш код менее понятным).

Более важной ситуацией с завершением рабочих потоков является то, что вы пытаетесь закрыть приложение, когда рабочий поток находится в состоянии ожидания.Поток не будет пробужден, если вы просто вызовете Terminate, обычно вы должны использовать дополнительный объект синхронизации (обычно событие) для пробуждения рабочего потока.

И еще одно замечание - в

* нет необходимости1006 *

Если вы посмотрите на код TThread.Destroy, он вызывает Terminate и WaitFor, поэтому достаточно

    MyThread.Free;

(по крайней мере, в Delphi 2009 нет источников Delphi 7 для проверки).


Обновлено

Прочитать Mghie ответ.Рассмотрим следующую ситуацию (лучше в системе с 1 ЦП):

выполняется основной поток

procedure TMainForm.Close;
begin
  if not MyThreadReady then
  begin
    MyThread.Terminate;
    MyThread.WaitFor;
    MyThread.Free;
  end;
end;

, он проверил значение MyThreadReady (неверно) и был отключен планировщиком.

Теперь планировщик переключается на рабочий поток;он выполняет

  Synchronize(ThreadFinished);

и заставляет планировщик переключиться обратно в основной поток.Основной поток продолжает выполнение:

    MyThread.Terminate;   // no problem
    MyThread.WaitFor;     // ???
    MyThread.Free;

Можете ли вы сказать, что произойдет на WaitFor?Я не могу (требует более глубокого изучения источников TThread, чтобы ответить, но на первый взгляд выглядит как тупик).

Ваша настоящая ошибка в чем-то другом - вы написали ненадежный код и пытаетесь выяснить, верен он или нет.Это плохая практика с потоками - вместо этого вы должны научиться писать надежный код.

Что касается ресурсов - когда TThread (с FreeOnTerminate = False) завершается, единственными ресурсами, которые остаются выделенными, является дескриптор потока Windows (он не использует значительные ресурсы Windows после завершения потока) и объект Delphi TThread в памяти,Не большая цена, чтобы быть на безопасной стороне.

0 голосов
/ 25 августа 2010

Я бы сказал, что смешивание моделей просто не рекомендуется.Вы либо используете FreeOnTerminate и никогда больше не трогаете поток, либо нет.В противном случае вам нужен защищенный способ связи между ними.

Так как вы хотите точно контролировать переменную потока, не используйте FreeOnTerminate.Если ваш поток завершает работу раньше времени, очистите локальные ресурсы, которые он использовал, как обычно, а затем просто дайте основному потоку освободить дочерний поток, когда приложение будет завершено.Вы получите лучшее из обоих миров - ресурсы, освобожденные дочерним потоком, как только это будет возможно, и не беспокойтесь о синхронизации потока.(И он получил дополнительный бонус за то, что он намного проще в дизайне / коде / понимании / поддержке ...)

0 голосов
/ 25 августа 2010

Честно говоря, ваш


... 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 );

и освободите свою ветку, как всегда.

С уважением Рафаэль

...