2012-01-10 1 views
8

В данном примере я получаю исключение при вызове AThread.Free.Тема: Ручка Invalid (6) при попытке освободить приостановленный поток

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, прежде чем уничтожить себя. Какой в ​​этом смысл?

+0

Во-первых, вы получаете какие-либо ошибки/предупреждения для этого? TThread.Execute является абстрактным в D2009. IME, вы должны получить предупреждение о создании экземпляров с абстрактными методами. Обычно TThread.Execute переопределяется в классе потомков TThread, и это потомок, который создается экземпляром. Я никогда не пытался создать экземпляр TThread напрямую - я уверен, что какое-то исключение будет поднято в потоке конструирования, построенном потоке или обоим. –

+4

Вы можете подождать, чтобы установить 'FreeOnTerminate' до того, как вы назовете' Resume'. –

+0

Я думаю, что уничтожить вызовы возобновить, потому что если поток был приостановлен, он не может быть правильно уничтожен в таком состоянии. –

ответ

15

Вы не можете установить 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; 
+0

Я думал об этом.Вероятно, вы правы, но контроль потока Delphi, особенно с завершением, был (и, вероятно, еще есть), таким беспорядком, что я не посмел опубликовать его. Я очень кратко посмотрел на TThread в «классах» и решил не смотреть дальше, если бы нашел что-то. –

+0

@MartinJames Это именно то, что происходит. Деструктор работает дважды. Это никогда не заканчивается хорошо. –

+0

@David Если я не вызываю AThread.Free в этом конкретном примере, я получаю утечку памяти. Итак, как я мог освободить поток дважды? – Wodzu

5

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

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

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; 

Теперь посмотрим на последнюю строку - вы call destructor (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; 

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

+0

Спасибо, Серж, в отношении пункта 1: Я верю, что могу, если я переведу его непосредственно перед Resume() или даже сразу после этого, он будет работать. Но спасибо за ответ в любом случае, я сейчас понимаю проблему. +1 – Wodzu

+0

После того, как вы назовете 'Start' (просьба называть' Start', а не 'Resume'), это неправильно. Поток может завершиться, и тогда будет слишком поздно устанавливать 'FreeOnTerminate'. Затем вы пропустите поток и дескриптор ОС. –

+1

+1, @David, в D2009 нет (TT) в D2009 (по мере того, как Q отмечен);) Это было с D2010, поэтому 'TThread.Resume' здесь верно. – TLama