【问题标题】:Thread Error: The Handle is Invalid (6) when trying to Free a suspended thread线程错误:尝试释放挂起的线程时句柄无效 (6)
【发布时间】:2012-01-10 14:59:56
【问题描述】:

在给定的示例中,我在调用 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. 在给定的示例中,我应该如何释放 AThreadTThread 实例?

  2. 我不明白,为什么TThread.Destroy 在自我毁灭之前调用Resume。这有什么意义?

【问题讨论】:

  • 首先,您是否收到任何错误/警告? TThread.Execute 在 D2009 中是抽象的。 IME,您应该收到有关使用抽象方法构造实例的警告。通常, TThread.Execute 在 TThread 后代类中被覆盖,并且被实例化的是那个后代。我从来没有尝试过直接创建 TThread 的实例——我很确定在构造线程、构造线程或两者上都会引发一些异常。
  • 你可以等到设置FreeOnTerminate,直到你打电话给Resume
  • 我猜销毁调用会恢复,因为如果一个线程被挂起,它就不能在这种状态下被正确地销毁。
  • 创建挂起的线程是自找麻烦。如果您在创建线程之前执行设置阶段,然后将其创建为非挂起,那么所有问题都消失了。 FreeOnTerminate 可以在 TThread.Create 构造函数中设置。
  • @LURD 实现这一目标的简单方法是将所有设置移动到构造函数中。

标签: delphi exception delphi-2009 tthread


【解决方案1】:

您不能将FreeOnTerminate 设置为True 在线程实例上调用Free。你必须做一个或另一个,但不能两者都做。就目前而言,您的代码会破坏线程两次。绝对不能两次销毁一个对象,当然,当析构函数第二次运行时,就会发生错误。

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

正如 Martin 在评论中指出的那样,您不能直接创建 TThread,因为 TThread.Execute 方法是抽象的。此外,您不应使用已弃用的Resume。使用Start 开始执行挂起的线程。

我个人不喜欢使用FreeOnTerminate。使用此功能会导致线程在创建它的不同线程上被销毁。当您想忘记实例引用时,通常会使用它。这让您不确定当您的进程终止时线程是否已被销毁,甚至在进程终止期间它是否正在终止并释放自己。

如果您必须使用FreeOnTerminate,那么您需要确保在将FreeOnTerminate 设置为True 后不要调用Free。所以显而易见的解决方案是在调用Start 之后立即将FreeOnTerminate 设置为True,然后忘记线程实例。如果您在准备开始之前有任何异常,那么您可以安全地释放线程,因为那时您的 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;

【讨论】:

  • 我想过。您可能是对的,但 Delphi 线程控制,尤其是终止时,已经(并且可能仍然是)如此混乱,以至于我不敢发布它。我非常简短地查看了“类”中的 TThread,并决定不再深入研究以防万一。
  • @MartinJames 这正是发生的事情。析构函数运行两次。这永远不会结束。
  • @David 如果我在这个特定的例子中不调用 AThread.Free,我就会遇到内存泄漏。那么我怎样才能释放线程两次呢?
  • 同样明显,可以在 except 处理程序中设置 FreeOnTerminate False。
  • @Sertac 是的,这也可以,但我认为在线程构造函数中移动代码确实是最好的方法。
【解决方案2】:

你的情况很复杂。

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

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

由于TerminateResume之前被调用,所以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;

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

【讨论】:

  • 谢谢 Serg,关于第 1 点:我相信我可以,如果我在 Resume() 之前甚至之后立即移动它,它会起作用。但是无论如何感谢您的回答,我现在更好地理解了这个问题。 +1
  • 调用Start后(请调用Start而不是Resume)是不对的。线程可能完成,然后设置FreeOnTerminate 为时已晚。然后你会泄露线程和操作系统句柄。
  • +1,@David,在 D2009 中还没有 TThread.Start(因为 Q 已被标记);)从 D2010 开始,所以 TThread.Resume 是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-09
  • 1970-01-01
  • 2013-08-01
  • 1970-01-01
  • 2016-11-17
  • 1970-01-01
相关资源
最近更新 更多