12

We have 2 applications, the first one is VCL project, the other is a windows service.

In the VCL project we do:

try
except
  on E: Exception do
    // do something with E.Message
end

but in the windows service (which uses several threads) we use:

try
except
    // do something with Exception(ExceptObject).Message
end

The information I got from my coworkers is that "We must use ExceptObject in threads and E: Exception in applications that use GUI". But I couldn't find anything regarding this.

I found an example here http://edn.embarcadero.com/article/10452 where it uses an instance variable to store the exception and it uses the ExceptObject, but gives no explanation why.

Is this ExceptObject even thread-safe (since it comes from the unit 'System')?

So what is the right way to handle exceptions in Delphi and why is there more than one way to do it?

Martijn
  • 13,225
  • 3
  • 48
  • 58
Lucas Steffen
  • 1,244
  • 2
  • 10
  • 22
  • The reason the edn article uses ExceptObject is the temporary exception instance is not accessible where the code handles the exception. Another way could be to pass the temporary variable to the procedure. Can't say anything about your service. – Sertac Akyuz Feb 19 '18 at 21:43
  • 2
    I think the edn code is wrong. It should use AcquireExceptionObject, the reference FException there stores may not be valid when the proc is synched with the main thread. – Sertac Akyuz Feb 19 '18 at 21:44
  • 1
    It's VCL not VLC and threading or GUI is never a driver for such choices. – David Heffernan Feb 19 '18 at 21:45
  • 3
    "*We must use ExceptObject in threads and E: Exception in applications that use GUI*" - that is completely false. `on E: Exception` works just fine in all threads whether a GUI is being used or not. The exception handling system doesn't know or care about GUIs – Remy Lebeau Feb 19 '18 at 23:09
  • "The information I got from my coworkers is...". They stated _what_ they do without explaining ***"Why?"*** Remember, ***all programmers*** are _fallible_. The problem with not explaining _why_: is that it becomes impossible to hold the reasoning up to rational scrutiny. FTR my understanding of the `on E: ` syntax, is that it's syntactic sugar to access `ExceptObject` with a temporary local variable. Trying to do so manually via `ExceptObject` exposes you to the risk of invalid typecasts. Although it's convention, I don't believe you can guarantee `Exception(ExceptObject)` is safe. – Disillusioned Feb 19 '18 at 23:24
  • I'd like to add that I've read the article you linked (http://edn.embarcadero.com/article/10452). Unfortunately much of what's written ranges from deeply flawed to wrong. (Refer back to my previous comment about being fallible.) 1) The article is old, written in 1999, so predates `AcquireExceptionObject` 2) Nowadays `TThread` safely returns exceptions not handled in `Execute()` via `FatalException` property. 3) Exceptions escaping `Execute()` are unlikely to be direct cause of AVs. 4) Thread exceptions should generally not be shown in dialogs because user context is certain to be wrong. – Disillusioned Feb 20 '18 at 00:01

2 Answers2

10

There is no right way for exception handling. There is just one way. What might confuse you could be dealing with the exception object which is created, and which causes an exception to raise, but whose lifetime is the most important for you here.

In general, there's only two ways of dealing with those exception objects. Either you let them alive beyond the exception block scope and release them by yourself or let them free by the RTL when the exception block ends.

But to answer what I guess you've asked. Exception class isn't thread safe. And, your coworkers were wrong as no one is forced to use specific exception handling in threads. These rules are the same for all threads created by the process, no matter what. Just, those exception objects can be unstable within exception blocks:

1. Get the current exception object from the ExceptObject

The ExceptObject returns the current exception object. In practice, it may cause this; if you store such object reference into a variable inside an exception handler block and another exception will get raised within such block, that stored instance may become invalid. Which is quite unsafe.

But it doesn't mean you could not take a reference of such object and pass it to another thread by using some synchronization mechanisms (since it's not a thread safe class) and work with it there. You just need to take care that no other exception will be raised because that would invalidate the previously stored object so as you must take care of staying inside the exception handler from the caller's point of view and you must use a kind of thread synchronization mechanism.

So actually working with the exception object acquired from an on expression can be more stable than using ExceptObject. But the same rules applies here as well; you'd need to synchronize the object instance from the on expression with another thread (since it's not a thread safe class), but in such case, object acquired from the on expression won't get changed unlike the ExceptObject one can be within a certain exception block.

2. Retain exception object by using AcquireExceptionObject

The AcquireExceptionObject function allows you to keep the exception object alive even out of the exception block.

For an exception handling when speaking about thread synchronization, I'd suggest you using the AcquireExceptionObject function which makes the exception object free to consume, even after the exception block ends. For you that brings the only responsability, free such acquired object by calling the ReleaseExceptionObject procedure or raising the exception by this object again.

Victoria
  • 7,822
  • 2
  • 21
  • 44
9

Victoria is absolutely correct.

Personally, I have a strong preference for this idiom:

try
  ...
   except
     // IO error
     On E : EInOutError do
       ShowMessage('IO error : '+E.Message);
     // Division by zero
     On E : EDivByZero do
       ShowMessage('Div by zero error : '+E.Message);
     // Catch other errors
     else
       ShowMessage('Unknown error');
   end;

To elaborate:

  1. Victoria said "There is no right way for exception handling. There is just one way." That's absolutely correct.

  2. The advice you got about "use one syntax for threads, and the other for GUIs" is simply wrong. There is no "different syntax" for "threads" vs. "GUI". That's nonsense :(

  3. I prefer using on : MyExceptionType in an exception block.

  4. I also prefer to differentiate different exception types, whenever/wherever possible.

  5. The example you cited, http://edn.embarcadero.com/article/10452, deals with how to avoid a possible access violation if you don't handle the exception within that particular thread. Saving the exception instance in a member variable helps mitigate this problem.

The following link might help clarify:

Ilyes
  • 14,640
  • 4
  • 29
  • 55
paulsm4
  • 114,292
  • 17
  • 138
  • 190