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

yan*_*kee 3 delphi delphi-7 tthread

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

private
  //...
  FExceptionClass: ExceptClass;  // --> Class of Exception
  FExceptionMessage: String;
  //...
Run Code Online (Sandbox Code Playgroud)

我以为我可以raiseOnTerminate事件中遇到类似的异常,以便主线程可以处理它(这是一个简化版本):

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;
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*nan 5

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

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

通过OnTerminate调用,在主线程上调用线程的事件处理程序Synchronize.现在,您的OnTerminate事件处理程序正在引发异常.所以我们需要弄清楚异常如何传播.

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

try
  SyncProc.SyncRec.FMethod; // this ultimately leads to your OnTerminate
except
  SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
end;
Run Code Online (Sandbox Code Playgroud)

所以,CheckSynchronize抓住你的例外并把它藏起来FSynchronizeException.然后继续执行,FSynchronizeException稍后提出.事实证明,这个被藏起来的例外被提出来了TThread.Synchronize.最后的垂死行为TThread.Synchronize是:

if Assigned(ASyncRec.FSynchronizeException) then 
  raise ASyncRec.FSynchronizeException;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


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

实现TThread将调用包装Execute在try/except块中.这是在ThreadProc函数中Classes.相关代码是:

try
  Thread.Execute;
except
  Thread.FFatalException := AcquireExceptionObject;
end;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

而且要明确的QueueExceptionToMainThread是,您必须编写一些功能!