6

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 ?

zeus
  • 12,173
  • 9
  • 63
  • 184

4 Answers4

10

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Works great when using a `TThread` class, but OP is using `CreateAnonymousThread`, so might want to point that out... – Jerry Dodge Sep 18 '18 at 16:01
  • 2
    @JerryDodge I'm aware that the OP is using. [`CreateAnonymousThread()`](http://docwiki.embarcadero.com/Libraries/en/System.Classes.TThread.CreateAnonymousThread). It returns a suspended `TThread` object, you can tweak its properties and assign event handlers to it before calling `Start()` on it – Remy Lebeau Sep 18 '18 at 16:03
  • Never knew that (FatalException property...). I think I still prefer catching and handling them in place, though. This seems less controlled, generally. – J... Sep 18 '18 at 16:12
  • @J... It depends on your needs. `Execute` is wrapped in a `try..except` block, and `OnTerminate` is synced with the UI thread. If you don't need that sync, you can skip the event – Remy Lebeau Sep 18 '18 at 16:18
  • @RemyLebeau thanks ! yes their is FatalException but anyway it's also painless to update all existing thread to override in all of them the ThreadTerminated than using a try .. finally in each of them :( – zeus Sep 18 '18 at 18:06
  • @RemyLebeau Yes, I see the appeal. I think what bothers me the most is that it introduces a new error handling pattern which is specific (to TThread) rather than idiomatic. It's also more awkward to debug (by the time you break in the handler you're in a different thread, different call stack...). The more I think about it the more reasons there are to avoid this, it seems. – J... Sep 18 '18 at 18:16
  • @j... see my answer below, i finally decide to do it like this. exception will be handled in the current thread and thus will permit us to retrieve the current stack trace – zeus Sep 18 '18 at 19:19
  • Is it OK to `raise` the caught exception in `OnTerminate` (in this case `ThreadTerminated`)? Excuse me! Just found [the answer](https://stackoverflow.com/a/17947776/2093077). – saastn Jan 29 '19 at 12:50
6

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;
J...
  • 30,968
  • 6
  • 66
  • 143
1

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; 
zeus
  • 12,173
  • 9
  • 63
  • 184
-1

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

zeus
  • 12,173
  • 9
  • 63
  • 184
  • 3
    Modifying the RTL source is arguably a bad idea if you want your code to work when compiled on any system other than your own. – J... Sep 18 '18 at 20:19
  • @J... yes i agree, especially when you upgrade delphi! But I can not imagine any descent app using firemonkey who didn't touche the RTL source ! – zeus Sep 19 '18 at 06:44