Ошибка потока: Неверный дескриптор (6) при попытке освободить приостановленный поток - PullRequest
8 голосов
/ 10 января 2012

В данном примере я получаю исключение при звонке 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 ]

15 голосов
/ 10 января 2012

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

В данном случае происходит то, что, поскольку вы создали приостановленный поток, ничего не произойдет, пока вы явно не освободите поток. Когда вы это сделаете, деструктор возобновляет поток, ожидает его завершения. Это приводит к повторному вызову Free, потому что для FreeOnTerminate установлено значение True. Этот второй вызов Free закрывает дескриптор. Затем вы возвращаетесь в процесс потока, и он вызывает 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;
5 голосов
/ 10 января 2012

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

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

  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 метод) должен выполняться в контексте потока, пока поток завершается.

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

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

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

...