Вопрос по – Правильный способ уничтожения объекта TThread

15

Этот вопрос может показаться тривиальным, но я надеюсь, вы его не проигнорируете.
Прежде чем уничтожить объект TThread, обычно необходимо дождаться завершения потока, вызвавшего метод TThread.Execute (), поскольку только тогда мы можем быть уверены, что, например, к объектам, уничтоженным внутри деструктора класса, больше нет доступа. Поэтому необходимо вызвать Terminate, чтобы установить флаг Termination, который поток должен проверить, чтобы узнать, выходить или нет, а затем вызвать метод WaitFor ().

Поскольку поток может быть приостановлен, я думаю, что было бы целесообразно возобновить его перед вызовом WaitFor, иначе вызывающий поток будет заблокирован. И поскольку поток может быть приостановлен несколько раз, он должен быть возобновлен столько же раз, верно?

while Suspended do
  Resume;

Если поток был создан приостановленным, нам не нужно беспокоиться о том, что метод TThread.Execute () будет вызываться, когда мы возобновляем поток только для его прекращения - не будет (пожалуйста, исправьте меня, если я ошибаюсь).

То, что я сказал, предлагает использовать следующие строки кода для каждого освобождаемого объекта TThread:

MyThread.Terminate;
while MyThread.Suspended do
  MyThread.Resume;
MyThread.WaitFor;
MyThread.Free;

К сожалению, когда мы уничтожаем наше приложение, которое создало несколько потоков, написание такого фрагмента кода для каждого уничтожаемого объекта TThread излишне делает код очень длинным и, возможно, даже непрозрачным.

Поэтому я пришел к выводу, что все это можно поместить в переопределенный деструктор класса TThread, благодаря которому было бы достаточно вызвать MyThread.Free (или MyThread.Terminate, если задано MyThread.FreeOnTerminate), не заботясь о том, уничтоженный объект является объектом TThread или нет:

destructor TMyThread.Destroy;
begin
  //if FreeOnTerminate, the calling thread cannot wait for itself
  if GetCurrentThreadId <> ThreadId then
  begin
    Terminate;
    while Suspended do
      Resume;
    WaitFor;
  end;

  {free all objects created in this class}

  inherited Destroy;
end;

Прошу меня задать такой простой вопрос. Я хотел бы, однако, узнать ваше мнение об этом способе - я надеюсь, универсальный способ - уничтожить объекты TThread. Я задаю этот вопрос, потому что я узнал из кодов моих коллег по работе, что они обычно использовали первый пример кода для уничтожения таких объектов, но они никогда не использовали, чтобы проверить, не приостановлены ли ожидающие потоки, что я считал немного опасным, если потоки может быть приостановлено где-то в коде. Поэтому я попытался найти универсальный способ уничтожения объектов этого класса, который сделал бы код более понятным и безопасным. Надеюсь, хуже не стало - как ты думаешь?

Спасибо за ваши предложения заранее.

Напоминание от слов лорда Полония Гамлета: краткость - это душа ума Argalatyr

Ваш Ответ

2   ответа
8

Не существует универсального способа остановить поток, так же как нет универсального способа (изящно) остановить процесс. Каждый отличается.

Для некоторых потоков достаточно установить егоTerminated собственность черезTerminate метод. Другие потоки, однако, вызывают такие функции, какGetMessage или жеMsgWaitForMultipleObjects, который будет блокироваться до тех пор, пока что-то не произойдет, например, о прибытии сообщения или получении указателя ядра.TThread.Terminate не может заставить ни одну из этих вещей произойти, поэтому она не может заставить эти потоки перестать работать. Когда я писал подобные потоки, я предоставлял свои собственные функции для уведомления их о прекращении работы. Я мог бы позвонитьPostThreadMessage принудительно отправить сообщение в очередь потока, или я мог бы сигнализировать о событии, предоставленном классом потока, для уведомления его о завершении запроса.

Не беспокойтесь о возобновлении приостановленной нити. Вы действительно не должны приостанавливать их в любом случае. Единственный безопасный способ приостановить поток - это приостановить сам поток, и как только вы это сделаете, вы гарантированно будете иметь по крайней мере два потока, контролирующих время выполнения потока: сам поток, чтобы приостановить его, и по крайней мере один другой поток, чтобы возобновить это снова. Поток должен контролировать собственное выполнение.

Было бы здорово, еслиTThread.Terminate были виртуальными. Тогда каждый класс потока может предоставить собственный способ уведомления о прекращении работы. Некоторые могут просто установитьTerminatedи другие могут публиковать сообщения, сигнализировать о событиях или делать все, что им нужно. Как бы то ни было, не виртуальный метод не очень хорошо работает с потоками, которые проводят много времени в ожидании других вещей. Текущий способ работает только для потоков, которые могут частоpoll ихTerminated свойства.

Некоторые темы имеют своиFreeOnTerminate свойства установлены. Для этих потоков ваш код небезопасен. Технически это небезопасноany методы для таких объектов, так как поток может завершиться в любое время. Но даже если вы знаете, что поток все еще работает и объект потока все еще существует, объект определенно прекратит свое существование через некоторое время после вызоваTerminate, Вы не можете позвонитьWaitFor на объекте потока со свободным завершением, и вы определенно не можете вызватьFree.

8

Многое из того, что вы предлагаете, уже выполнено в деструкторе TThread.Destroy, и вызов TMyThread.free сделает то, что вы предлагаете. Чтобы очистить любые объекты, принадлежащие классу потока, вы можете выполнить это в событии OnTerminate, которое будет вызываться как часть логики завершения потока.

Да, это правда, однако я проверил, что только когда поток создается приостановленным, он возобновляется при вызове деструктора. Когда я приостанавливаю его во время его работы, вызов деструктора без его возобновления вызовет тупик. И именно поэтому я искал, скажем, универсальный способ уничтожения объектов TThread, который подойдет для любого класса, который наследуется от TThread. Mariusz Schimke
@Mariusz: попробуйте написать код без Suspend () и Resume (), и у вас не возникнет этой проблемы. Эти методы могут быть очень проблематичными, как вы уже нашли, и всегда есть способ кодировать без них. Вместо этого пусть поток блокируется на одном или нескольких системных примитивах синхронизации. Или используйте циклы сообщений, здесь есть несколько ответов с информацией о многопоточности на SO в [delphi].

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