Ошибка потока: Неверный дескриптор (6) при попытке освободить приостановленный поток

В данном примере я получаю исключение при вызове AThread.Free.

program Project44;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Windows;

type
  TMyException = class(Exception);

var
  AThread: TThread;
begin
  AThread := TThread.Create(True);
  try
    AThread.FreeOnTerminate := True;
    //I want to do some things here before starting the thread
    //During the setup phase some exception might occur, this exception is for simulating purpouses
      raise TMyException.Create('exception');
  except
    AThread.Free; //Another exception here
  end;
end.

У меня есть два вопроса:

  1. Как я должен освободиться AThread экземпляр TThread в данном примере?

  2. Я не понимаю почему TThread.Destroy звонит Resume прежде чем уничтожить себя. Какой в ​​этом смысл?

2 ответа

Решение

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

Здесь происходит то, что, поскольку вы создали приостановленный поток, ничего не происходит, пока вы явно не освободите поток. Когда вы делаете это, деструктор возобновляет поток, ожидает его завершения. Это тогда приводит к Free вызывается снова, потому что вы установили FreeOnTerminate в True, Этот второй звонок Free закрывает ручку Затем вы вернетесь в ветку proc, и это вызовет ExitThread, Это терпит неудачу, потому что дескриптор потока был закрыт.

Как Мартин указывает в комментарии, вы не должны создавать TThread непосредственно с TThread.Execute Метод абстрактный. Кроме того, вы не должны использовать Resume что устарело. использование Start начать выполнение приостановленной нити.

Лично я не люблю использовать FreeOnTerminate, Использование этой функции приводит к удалению потока в другом потоке, из которого он был создан. Вы обычно используете его, когда хотите забыть о ссылке на экземпляр. Это оставляет вас в неведении относительно того, был ли поток уничтожен, когда ваш процесс завершается, или даже если он завершается и освобождается во время завершения процесса.

Если вы должны использовать FreeOnTerminate тогда вам нужно убедиться, что вы не звоните Free после установки FreeOnTerminate в True, Таким образом, очевидное решение состоит в том, чтобы установить FreeOnTerminate в True сразу после звонка Start а затем забыть об экземпляре потока. Если у вас есть какие-либо исключения, прежде чем вы готовы начать, то вы можете безопасно освободить поток, так как вы FreeOnTerminate все равно будет False в таком случае.

Thread := TMyThread.Create(True);
Try
  //initialise thread object
Except
  Thread.Free;
  raise;
End;
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil;

Более элегантный подход - перенести всю инициализацию в TMyThread конструктор. Тогда код будет выглядеть так:

Thread := TMyThread.Create(True);
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil;

Ситуация очень сложная в вашем случае.

Во-первых, вы фактически не освобождаете приостановленную нить; поток возобновляется в деструкторе:

  begin
    Terminate;
    if FCreateSuspended then
      Resume;
    WaitFor;
  end;

поскольку Terminate называется раньше Resume, Execute Метод никогда не запускается, и поток завершается сразу после возобновления:

  try
    if not Thread.Terminated then
    try
      Thread.Execute;
    except
      Thread.FFatalException := AcquireExceptionObject;
    end;
  finally
    Result := Thread.FReturnValue;
    FreeThread := Thread.FFreeOnTerminate;
    Thread.DoTerminate;
    Thread.FFinished := True;
    SignalSyncEvent;
    if FreeThread then Thread.Free;

Теперь посмотрите на последнюю строку - вы вызываете деструктор (Thread.Free) от самого деструктора! Фантастическая ошибка!


Чтобы ответить на ваши вопросы:

  1. Вы просто не можете использовать FreeOnTerminate:= True в вашем коде;
  2. Вы должны спросить Embarcadero, почему TThread разработан так; мое предположение - некоторый код (DoTerminate method) должен выполняться в контексте потока, пока поток завершается.

Вы можете отправить запрос функции в КК: добавить FFreeOnTerminate:= False в TThread.Destroy реализация:

destructor TThread.Destroy;
begin
  FFreeOnTerminate:= False;
// everything else is the same
  ..
end;

Это должно предотвратить рекурсивный вызов desctructor и сделать ваш код корректным.

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