I have for habit to execute anonymous thread like :
TThread.CreateAnonymousThread(
procedure
begin
.....
end).start;
But the problem is that if some unhandled exception will raise during the execution, then i will be not warned about it! For the main thread we have Application.OnException. Do we have something similar for background thread ?
TThread has a public FatalException property:
If the Execute method raises an exception that is not caught and handled within that method, the thread terminates and sets FatalException to the exception object for that exception. Applications can check FatalException from an OnTerminate event handler to determine whether the thread terminated due to an exception.
For example:
procedure TMyForm.DoSomething;
begin
...
thread := TThread.CreateAnonymousThread(...);
thread.OnTerminate := ThreadTerminated;
thread.Start;
...
end;
procedure TMyForm.ThreadTerminated(Sender: TObject);
begin
if TThread(Sender).FatalException <> nil then
begin
...
end;
end;
N̶o̶,̶ ̶t̶h̶e̶r̶e̶ ̶i̶s̶ ̶n̶o̶t̶h̶i̶n̶g̶ ̶s̶i̶m̶i̶l̶a̶r̶ ̶f̶o̶r̶ ̶a̶ ̶b̶a̶c̶k̶g̶r̶o̶u̶n̶d̶ ̶t̶h̶r̶e̶a̶d̶.̶ (Thanks, Remy!)
The best solution is to always be absolutely certain to never let an exception escape from a thread. Ideally, your thread procedures should look something like this :
TThread.CreateAnonymousThread(
procedure
begin
try
{ your code}
except
{on E : Exception do}
{... handle it!}
end;
end).start;
Ok, finally I got the best answer :
Assign the global ExceptionAcquired procedure to our own implementation (it is nil by default). This procedure gets called for unhandled exceptions that happen in other threads than the main thread.
ExceptionAcquired := MyGlobalExceptionAcquiredHandler;
The answer of J... and Remy Lebeau are good with what delphi offer, but i need a little more and I finally decide to modify a little the unit System.Classes
var
ApplicationHandleThreadException: procedure (Sender: TObject; E: Exception) of object = nil;
function ThreadProc(const Thread: TThread): Integer;
...
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
if assigned(ApplicationHandleThreadException) and
assigned(Thread.FFatalException) and
(Thread.FFatalException is Exception) and
(not (Thread.FFatalException is EAbort)) then
ApplicationHandleThreadException(Thread, Exception(Thread.FFatalException));
end;
in this way you just need to assign ApplicationHandleThreadException to handle unhandled exception raise in any TThread. You don't need to be worry about the multi thread because global var like ExceptAddr are declared as threadvar so everything work fine, even to retrieve the stack trace !
https://quality.embarcadero.com/browse/RSP-21269
Related
I am reading "Delphi High performance" and there is something that I am missing. Given this code as test:
type TTest = class(TThread)
private
amemo: TMemo;
public
constructor Create(ss: boolean; memo: TMemo);
protected
procedure Execute; override;
end;
constructor TTest.Create(ss: boolean; memo: TMemo);
begin
inherited Create(ss);
FreeOnTerminate := true;
amemo := memo;
end;
procedure TTest.Execute;
var i: uint32;
begin
inherited;
i := 0;
while not Terminated do
begin
Inc(i);
Synchronize(procedure
begin amemo.Lines.Add(i.ToString) end);
Sleep(1000);
end;
end;
Very simply, this thread prints some numbers in a memo. I start the thread suspended and so I have to call this piece of code:
procedure TForm1.Button1Click(Sender: TObject);
begin
thread := TTest.Create(true, Memo1);
thread.Start;
end;
I have always stopped the thread calling thread.Terminate; but reading the book I see that Primoz stops a thread like this:
procedure TForm1.Button2Click(Sender: TObject);
begin
thread.Terminate;
thread.WaitFor; //he adds this method call
//FreeAndNil(thread)
//there is the above line as well in the code copied from the book but I have removed it since I have set FreeOnTerminate := true (so I dont have to worry about freeing the obj).
end;
At this point, if I run the code using only Terminate I have no problems. If I run the code with Terminate + WaitFor I get this error:
I have read more coding in delphi too and I see that Nick Hodges just makes a call to Terminate;. Is calling Terminate; enough to safey stop a thread? Note that I've set FreeOnTerminate := true so I don't care about the death of the object. Terminated should stop the execution (what is inside execute) and so it should be like this:
Call Terminated
Execute stops
Thread stops execution
Thread is now free (FreeOnTerminate := true)
Please tell me what I'm missing.
Note.
In the book the thread doesn't have FreeOnTerminate := true. So the thread needs to be freed manually; I guess that this is the reason why he calls
thread.Terminate;
thread.WaitFor;
FreeAndNil(thread)
I agree on Terminate (stop the thread= and FreeAndNil (free the object manually) but the WaitFor?
Please tell me what I'm missing.
The documentation for FreeOnTerminate explicitly says that you cannot use the Thread in any way after Terminate.
That includes your WaitFor call, which would work on a possibly already free'd object. This use-after-free can trigger the error above, among other even more "interesting" behaviours.
If exception is raised in finally block of code, is the rest of finally block executed, or not?
try
statementList1
finally
command;
command_that_raises;
critical_command;
end
Will critical_command be executed?
Manual talks only about exceptions, not execution of code:
If an exception is raised but not handled in the finally clause, that exception is propagated out of the try...finally statement, and any exception already raised in the try clause is lost. The finally clause should therefore handle all locally raised exceptions, so as not to disturb propagation of other exceptions.
See the following confirmation:
procedure TForm6.Button1Click(Sender: TObject);
begin
try
ShowMessage('begin');
finally
ShowMessage('enter');
raise Exception.Create('raise');
ShowMessage('end');
end;
end;
And now for this case:
procedure RaiseAndContinue;
begin
try
raise Exception.Create('raise');
except
end;
end;
procedure TForm6.Button1Click(Sender: TObject);
begin
try
ShowMessage('begin');
finally
ShowMessage('enter');
RaiseAndContinue;
ShowMessage('end');
end;
end;
The short answer is: unless you handle that exception then No the code will not be executed.
I'm using Delphi 7 and in an attempt to handle all the possible exceptions being thrown during the run of the program. I used Application.OnException := HandlerProcedure; to handle exceptions but when exception occurs, HandlerProcedure never gets called. In order to assure if it really works, I raised exception after I assigned Application.OnException as below:
Application.OnException := HandlerProcedure;
raise Exception.Create('Exception');
and defined HandlerProcedure as:
procedure TFormMain.HandlerProcedure(Sender: TObject; E: Exception);
begin
ShowMessage('Exception.');
Exit;
end;
But HandlerProcedure never gets called. How can I make it handle all the exceptions?
If you want to intercept ALL exceptions, you need to implement a RTLUnwindProc low-level procedure.
This is a bit low-level (e.g. it needs asm skills), so you should better rely on existing code. See this stack overflow question. I even put some reference code (including low-level asm, working with Delphi 7 and later under Win32) in my own answer.
Something is wrong in your code. The example from Embarcadero's website is working perfect.
{
In addition to displaying the exception message, which
happens by default, the following code shuts down the
application when an exception is not caught and handled.
AppException should be declared a method of TForm1.
}
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := AppException;
end;
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
Application.ShowException(E);
Application.Terminate;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
raise EPasswordInvalid.Create('Incorrect password entered');
end;
Also good practices on handling errors on Delphi are described here.
In order to further investigate the problem you have, you should take a look at this https://stackoverflow.com/questions/1259563/good-os-delphi-exception-handling-libraries
If you are using a third party exception handler such as madExcept, Application.OnException no longer fires. You must instead code TMadExceptionHandler.OnException event or directly call RegisterExceptionHandler.
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
The fundamental issue in this question, to my mind is:
What happens when you raise an exception in a thread's OnTerminate event handler.
A thread's OnTerminate event handler is invoked on the main thread, by a call to Synchronize. Now, your OnTerminate 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 from CheckSynchronize. The code that is relevant is this:
try
SyncProc.SyncRec.FMethod; // this ultimately leads to your OnTerminate
except
SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
end;
So, CheckSynchronize catches your exception and stashes it away in FSynchronizeException. Excecution then continues, and FSynchronizeException is later raised. And it turns out, that the stashed away exception is raised in TThread.Synchronize. The last dying act of TThread.Synchronize is:
if Assigned(ASyncRec.FSynchronizeException) then
raise ASyncRec.FSynchronizeException;
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 since TThread already does so.
The implementation of TThread wraps the call to Execute in an try/except block. This is in the ThreadProc function in Classes. The pertinent code is:
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
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:
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;
And just to be clear, QueueExceptionToMainThread is some functionality that you have to write!
AFAIK the OnTerminate event is called with the main thread (Delphi 7 source code):
procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;
The Synchronize() method is in fact executed in the CheckSynchronize() 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 time TMyThread.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 a TEvent or post a GDI message.
A synchonized call of an exception will not prevent the thread from being interrupted.
Anything in function ThreadProc after Thread.DoTerminate; will be omitted.
The code above has two test cases
One with commented / uncommented synchronized exception //**
The second with (un)encapsulated exception in the OnTerminate event, which will lead even to an omitted destruction if used unencapsulated.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyThread=Class(TThread)
private
FExceptionClass: ExceptClass;
FExceptionMessage: String;
procedure DoOnTerminate(Sender: TObject);
procedure SynChronizedException;
procedure SynChronizedMessage;
public
procedure Execute;override;
Destructor Destroy;override;
End;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TMyThread.SynChronizedException;
begin
Showmessage('> SynChronizedException');
raise Exception.Create('Called Synchronized');
Showmessage('< SynChronizedException'); // will never be seen
end;
procedure TMyThread.SynChronizedMessage;
begin
Showmessage('After SynChronizedException');
end;
procedure TMyThread.Execute;
begin
try
OnTerminate := DoOnTerminate; // first test
Synchronize(SynChronizedException); //** comment this part for second test
Synchronize(SynChronizedMessage); // will not be seen
raise Exception.Create('Thread Exception!!');
except
on E:Exception do
begin
FExceptionClass := ExceptClass(E.ClassType);
FExceptionMessage := E.Message;
end;
end;
end;
destructor TMyThread.Destroy;
begin
Showmessage('Destroy ' + BoolToStr(Finished)) ;
inherited;
end;
procedure TMyThread.DoOnTerminate(Sender: TObject);
begin
{ with commented part above this will lead to a not called destructor
if Assigned(FExceptionClass) then
raise FExceptionClass.Create(FExceptionMessage);
}
if Assigned(FExceptionClass) then
try // just silent for testing
raise FExceptionClass.Create(FExceptionMessage);
except
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
With TMyThread.Create(false) do FreeOnTerminate := true;
ShowMessage('Hallo');
end;
end.
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.
I have a threaded application and for some purpose I want to pass call stack information of a catched exception to a new custom exception:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, CallStackOfExceptionEAsString);
end;
end;
What is the best way to do this, preferably using EurekaLog? I am using Delphi 2006 btw.
EurekaLog exposes several event handlers like OnExceptionNotify.
You can implement these in your code. For example: procedure EurekaLogExceptionNotify(
EurekaExceptionRecord: TEurekaExceptionRecord; var Handled: Boolean);
Here you can see a TEurekaExceptionRecord which is defined in ExceptionLog.pas. But you maybe just own the non-source version which works just fine.
The record has a EurekaExceptionRecord.CallStack list. This proprietary list can be converted to TStringsusing the CallStackToStrings method which is also defined in the ExceptionLog unit.
Here is an example where I write the CallStack into a StringList.
CallStackList := TStringList.Create;
try
CallStackToStrings(EurekaExceptionRecord.CallStack, CallStackList);
LogMessage := 'An unhandled exception occured. Here is the CallStack.' + #13#10
+ CallStackList.Text;
finally
CallStackList.Free;
end;
At least from this starting point you should be able to investigate the exposed functions, records etc.. All information is accessible.
EurekaLog provides a function GetLastExceptionCallStack() (defined in unit ExceptionLog.pas).
Using this I have written the following function (based on example code here):
function GetLastEurekalogCallStackAsString(): string;
{$IFDEF EUREKALOG}
var
Stack: TEurekaStackList;
Str: TStringList;
{$ENDIF}
begin
{$IFDEF EUREKALOG}
Stack := GetLastExceptionCallStack();
try
Str := TStringList.Create;
try
CallStackToStrings(Stack, Str);
Result := Str.Text;
finally
FreeAndNil(Str);
end;
finally
FreeAndNil(Stack);
end;
{$ELSE}
Result := '';
{$ENDIF}
end;
So you can write:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, GetLastEurekalogCallStackAsString());
end;
end;
EurekaLog 7 has Chained Exception support, which is specifically designed for this task. Just enable it in options (it is enabled by default) and use:
try
// here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
Exception.RaiseOuterException(EMyException.Create(E.Message));
// for old IDEs:
// raise EMyException.Create(E.Message);
end;
end;