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

Этот вопрос может показаться тривиальным, но я надеюсь, что вы его не проигнорируете.
Перед уничтожением объекта TThread обычно необходимо подождать, пока поток, вызвавший метод TThread.Execute(), не завершится, поскольку только тогда мы можем быть уверены, что, например, объекты, уничтоженные внутри деструктора класса, больше не доступны. Поэтому необходимо вызвать Terminate, чтобы установить флаг Terminated, который поток должен проверить, чтобы узнать, следует ли выйти или нет, а затем вызвать метод 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. Я задаю эти вопросы, потому что я узнал из кодов моих товарищей по работе, что они обычно использовали первый пример кода для уничтожения таких объектов, но они никогда не использовали для проверки того, были ли ожидаемые нити, которые я считал немного опасными, если потоки может быть приостановлено где-то в коде. Поэтому я попытался найти универсальный способ уничтожения объектов этого класса, которые сделают код более понятным и безопасным. Надеюсь, я не сделал этого хуже - как вы думаете?

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

Ответы

Ответ 1

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

Ответ 2

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

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

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

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

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