线程错误:当尝试释放挂起的线程时,句柄无效(6)

时间:2012-01-10 14:59:56

标签: delphi exception delphi-2009 tthread

在给定示例中,我在调用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 个答案:

答案 0 :(得分:15)

您无法在线程实例上将FreeOnTerminate设置为True 调用Free。你必须做一个或另一个,但不是两个。因为它代表你的代码破坏线程两次。你必须永远不要销毁一个对象两次,当然,当析构函数第二次运行时,会发生错误。

这里发生的事情是,由于您创建了线程暂停,因此在您明确释放线程之前不会发生任何事情。当你这样做时,析构函数恢复线程,等待它完成。然后,这会导致再次调用Free,因为您将FreeOnTerminate设置为True。第二次调用Free会关闭句柄。然后返回线程proc并调用ExitThread。这会失败,因为线程的句柄已经关闭。

正如Martin在评论中指出的那样,您不能直接创建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;

答案 1 :(得分:5)

你的情况非常复杂。

首先,你实际上没有释放被挂起的线程;一个线程在析构函数中恢复:

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

由于在Terminate之前调用ResumeExecute方法永远不会运行,并且线程在恢复后立即终止:

  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方法)应该在线程上下文中执行 线程终止。

  3. 您可以向QC发送功能请求:将FFreeOnTerminate:= False添加到TThread.Destroy实施:

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

    这应该阻止递归的析构函数调用并使代码有效。