我可以在TThread的OnTerminate事件中引发异常吗?

时间:2013-07-30 07:24:57

标签: delphi delphi-7 tthread

我编写了一个TThread后代类,如果引发异常,则将异常的Class和Message保存在两个私有字段中

private
  //...
  FExceptionClass: ExceptClass;  // --> Class of Exception
  FExceptionMessage: String;
  //...

我以为我可以在raise事件中OnTerminate出现类似的异常,以便主线程可以处理它(这里是简化版):

procedure TMyThread.Execute;
begin
  try
    DoSomething;
    raise Exception.Create('Thread Exception!!');
  except
    on E:Exception do
    begin
      FExceptionClass := ExceptClass(E.ClassType);
      FExceptionMessage := E.Message;
    end;
  end;
end;

procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
  if Assigned(FExceptionClass) then
    raise FExceptionClass.Create(FExceptionMessage);
end;

我希望发生标准的异常处理机制(错误对话框), 但我得到了混合结果:对话框出现但后面跟着系统错误,或者 或者(更有趣的)对话框出现但调用线程的函数继续运行,好像异常从未引发过 我猜问题是关于调用堆栈。
这是个坏主意吗? 是否有另一种方法可以将线程异常与主线程分离,但是以标准方式再现它们? 谢谢

4 个答案:

答案 0 :(得分:5)

在我看来,这个问题的根本问题是:

  

在线程的OnTerminate事件处理程序中引发异常时会发生什么。

通过调用OnTerminate在主线程上调用线程的Synchronize事件处理程序。现在,您的OnTerminate事件处理程序正在引发异常。因此,我们需要弄清楚该异常的传播方式。

如果检查OnTerminate事件处理程序中的调用堆栈,您将看到它在CheckSynchronize的主线程上被调用。相关的代码是:

try
  SyncProc.SyncRec.FMethod; // this ultimately leads to your OnTerminate
except
  SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
end;

因此,CheckSynchronize会抓住您的异常并将其隐藏在FSynchronizeException中。然后继续执行,稍后提出FSynchronizeException。事实证明,在TThread.Synchronize中提出了隐藏的异常。 TThread.Synchronize的最后一个垂死行为是:

if Assigned(ASyncRec.FSynchronizeException) then 
  raise ASyncRec.FSynchronizeException;

这意味着您在主线程中引发异常的尝试已被框架阻止,该框架将其移回到您的线程上。现在,这是一个灾难,因为在执行raise ASyncRec.FSynchronizeException时,在这种情况下,没有异常处理程序处于活动状态。这意味着线程过程将抛出SEH异常。这会让房子倒塌。

所以,我从这一切得出的结论是以下规则:

永远不要在线程的OnTerminate事件处理程序中引发异常。

您必须找到一种不同的方式来在主线程中显示此事件。例如,将消息排队到主线程,例如通过调用PostMessage


顺便说一句,您不需要在Execute方法中实现异常处理程序,因为TThread已经这样做了。

TThread的实现在try / except块中包含对Execute的调用。这位于ThreadProc中的Classes函数中。相关代码是:

try
  Thread.Execute;
except
  Thread.FFatalException := AcquireExceptionObject;
end;

在捕获到异常后调用OnTerminate事件处理程序,因此您可以完全选择从那里重新显示它,尽管不是像我们上面发现的那样天真地提升它。

您的代码将如下所示:

procedure TMyThread.Execute;
begin
  raise Exception.Create('Thread Exception!!');
end;

procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
  if Assigned(FatalException) and (FatalException is Exception) then
    QueueExceptionToMainThread(Exception(FatalException).Message);
end;

为了清楚起见,QueueExceptionToMainThread是您必须编写的一些功能!

答案 1 :(得分:4)

AFAIK使用主线程调用OnTerminate事件(Delphi 7源代码):

procedure TThread.DoTerminate;
begin
  if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;

Synchronize()方法实际上是在CheckSynchronize()上下文中执行的,而在Delphi 7中,它将在远程线程中重新引发异常。

因此,在OnTerminate中引发异常是不安全的,或者至少没有任何用处,因为此时TMyThread.Execute已超出范围。

简而言之,您的Execute方法永远不会触发异常。

对于你的情况,我怀疑你不应该在OnTerminate中引发任何异常,而是设置一个全局变量(不是很漂亮),在一个线程安全的全局中添加一个项目列出(更好),和/或提出TEvent或发布GDI消息。

答案 2 :(得分:2)

同步调用异常不会阻止线程被中断。 <{1}}之后function ThreadProc中的任何内容都将被省略。

上面的代码有两个测试用例

  • 一个带注释/未注释的同步异常// **
  • 第二个在OnTerminate事件中带有(un)封装异常,如果使用未封装的话,甚至会导致省略的破坏。

Thread.DoTerminate;

答案 3 :(得分:0)

我不知道你为什么要在主线程中引发异常,但我会假设它是做最小的异常处理 - 我认为这就像显示Exception对象的ClassName和Message一样在UI上很好的方式。如果这是您想要做的全部,那么如果您在线程中捕获异常,然后将Exception.ClassName和Exception.Message strings保存到主线程上的私有变量。我知道这不是最先进的方法,但我已经做到了,我知道它有效。一旦线程因异常而终止,您就可以在UI上显示这两个字符串。您现在需要的是一种通知主线程工作线程已终止的机制。我过去使用消息实现了这一点,但我不记得具体细节。

而不是试图解决“如何通过做B来解决问题A?”您可以将您的情况重新定义为“我如何以可能的方式解决问题?”。

只是一个建议。希望它有助于你的情况。