Бесплатно 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;
9
задан CinCout 5 May 2017 в 03:53
поделиться

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. Первый не обязательно вызывается, и в этом случае ваш поток, возможно, продолжит работу и поддержит ваш процесс.

8
ответ дан 4 December 2019 в 14:26
поделиться

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


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

Я не фанат syncronise. Мне нравятся более важные разделы за гибкость при расширении кода для обработки большего количества общих данных.

В публичном разделе формы объявите:

ControlSection : TRTLCriticalSection;

При создании формы или в другом месте перед thread.create,

InitializeCriticalSection(ControlSection);

Затем, каждый раз, когда вы пишете в общий ресурс (включая вашу переменную MyThreadReady), выполняйте


EnterCriticalSection ( ControlSection );
  MyThreadReady := True; //or false, or whatever else
LeaveCriticalSection ( ControlSection );

Перед вы уходите (выход), звоните по номеру


DeleteCriticalSection ( ControlSection );

и, как всегда, освобождаете поток.

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

0
ответ дан 4 December 2019 в 14:26
поделиться

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

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

0
ответ дан 4 December 2019 в 14:26
поделиться

Нет, ваш код не очень хорош (хотя он, вероятно, будет работать в 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 на рука для проверки).


Обновлено

Прочитайте мой ответ.Рассмотрим следующую ситуацию (лучше на 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 в памяти. Не большие затраты, чтобы быть в безопасности.

2
ответ дан 4 December 2019 в 14:26
поделиться

Попросите основной поток назначить обработчик события 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;
4
ответ дан 4 December 2019 в 14:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: