Ошибка потока: Неверный дескриптор (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.
У меня есть два вопроса:
Как я должен освободиться
AThread
экземплярTThread
в данном примере?Я не понимаю почему
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
) от самого деструктора! Фантастическая ошибка!
Чтобы ответить на ваши вопросы:
- Вы просто не можете использовать
FreeOnTerminate:= True
в вашем коде; - Вы должны спросить Embarcadero, почему TThread разработан так; мое предположение - некоторый код (
DoTerminate
method) должен выполняться в контексте потока, пока поток завершается.
Вы можете отправить запрос функции в КК: добавить FFreeOnTerminate:= False
в TThread.Destroy
реализация:
destructor TThread.Destroy;
begin
FFreeOnTerminate:= False;
// everything else is the same
..
end;
Это должно предотвратить рекурсивный вызов desctructor и сделать ваш код корректным.