Освободите 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. Если ваш поток завершает работу раньше времени, очистите локальные ресурсы, которые он использовал, как обычно, а затем просто дайте основному потоку освободить дочерний поток, когда приложение будет завершено. Вы получите лучшее из обоих миров - ресурсы, освобожденные дочерним потоком, как только это будет возможно, и не беспокойтесь о синхронизации потока. (И он получил дополнительный бонус за то, что он намного проще в дизайне / коде / понимании / поддержке...)

Другие вопросы по тегам