I wrote a TThread
descendant class that, if an exception is raised, saves exception's Class and Message in two private fields
private
//...
FExceptionClass: ExceptClass; // --> Class of Exception
FExceptionMessage: String;
//...
I thought I could raise
a similar exception in the OnTerminate
event, so that the main thread could handle it (here is a simplified version):
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;
I expect that the standard exception handling mechanism occurs (an error dialog box),
but I get mixed results: The dialog appears but is followed by a system error, or
or (more funny) the dialog appears but the function that called the thread goes on as if the exception were never raised.
I guess that the problem is about the call stack.
Is it a bad idea?
Is there another way to decouple thread exceptions from the main thread but reproducing them the standard way?
Thank you
AFAIK the
OnTerminate
event is called with the main thread (Delphi 7 source code):The
Synchronize()
method is in fact executed in theCheckSynchronize()
context, and in Delphi 7, it will re-raise the exception in the remote thread.Therefore, raising an exception in
OnTerminate
is unsafe or at least without any usefulness, since at this timeTMyThread.Execute
is already out of scope.In short, the exception will never be triggered in your
Execute
method.For your case, I suspect you should not raise any exception in
OnTerminate
, but rather set a global variable (not very beautiful), add an item in a thread-safe global list (better), and/or raise aTEvent
or post a GDI message.The fundamental issue in this question, to my mind is:
A thread's
OnTerminate
event handler is invoked on the main thread, by a call toSynchronize
. Now, yourOnTerminate
event handler is raising an exception. So we need to work out how that exception propagates.If you examine the call stack in your
OnTerminate
event handler you will see that it is called on the main thread fromCheckSynchronize
. The code that is relevant is this:So,
CheckSynchronize
catches your exception and stashes it away inFSynchronizeException
. Excecution then continues, andFSynchronizeException
is later raised. And it turns out, that the stashed away exception is raised inTThread.Synchronize
. The last dying act ofTThread.Synchronize
is:What this means is that your attempts to get the exception to be raised in the main thread have been thwarted by the framework which moved it back onto your thread. Now, this is something of a disaster because at the point at which
raise ASyncRec.FSynchronizeException
is executed, in this scenario, there is no exception handler active. That means that the thread procedure will throw an SEH exception. And that will bring the house down.So, my conclusion from all this is the following rule:
Never raise an exception in a thread's
OnTerminate
event handler.You will have to find a different way to surface this event in your main thread. For example, queueing a message to the main thread, for example by a call to
PostMessage
.As an aside, you don't need to implement an exception handler in your
Execute
method sinceTThread
already does so.The implementation of
TThread
wraps the call toExecute
in an try/except block. This is in theThreadProc
function inClasses
. The pertinent code is:The
OnTerminate
event handler is called after the exception has been caught and so you could perfectly well elect to re-surface it from there, although not by naively raising it as we discovered above.Your code would then look like this:
And just to be clear,
QueueExceptionToMainThread
is some functionality that you have to write!A synchonized call of an exception will not prevent the thread from being interrupted. Anything in
function ThreadProc
afterThread.DoTerminate;
will be omitted.The code above has two test cases
I don't know why you want to raise the exception in the main thread, but I will assume it is to do minimal exception handling - which I would consider to be something like displaying the ClassName and Message of the Exception object in a nice way on the UI. If this is all you want to do then how about if you catch the exception in your thread, then save the Exception.ClassName and Exception.Message
strings
to private variables on the main thread. I know it's not the most advanced method, but I've done this and I know it works. Once the thread terminates because of the exception you can display those 2 strings on the UI. All you need now is a mechanism for notifying the main thread that the worker thread has terminated. I've achieved this in the past using Messages but I can't remember the specifics.Rather than try to solve "How do I solve problem A by doing B?" you could reframe your situation as "How do I solve problem A whichever way possible?".
Just a suggestion. Hope it helps your situation.