我编写了一个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;
我希望发生标准的异常处理机制(错误对话框),
但我得到了混合结果:对话框出现但后面跟着系统错误,或者
或者(更有趣的)对话框出现但调用线程的函数继续运行,好像异常从未引发过
我猜问题是关于调用堆栈。
这是个坏主意吗?
是否有另一种方法可以将线程异常与主线程分离,但是以标准方式再现它们?
谢谢
答案 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
中的任何内容都将被省略。
上面的代码有两个测试用例
Thread.DoTerminate;
答案 3 :(得分:0)
我不知道你为什么要在主线程中引发异常,但我会假设它是做最小的异常处理 - 我认为这就像显示Exception对象的ClassName和Message一样在UI上很好的方式。如果这是您想要做的全部,那么如果您在线程中捕获异常,然后将Exception.ClassName和Exception.Message strings
保存到主线程上的私有变量。我知道这不是最先进的方法,但我已经做到了,我知道它有效。一旦线程因异常而终止,您就可以在UI上显示这两个字符串。您现在需要的是一种通知主线程工作线程已终止的机制。我过去使用消息实现了这一点,但我不记得具体细节。
而不是试图解决“如何通过做B来解决问题A?”您可以将您的情况重新定义为“我如何以可能的方式解决问题?”。
只是一个建议。希望它有助于你的情况。