“Application.exe has stopped working” – almost everybody knows this windows dialog. The reason for this kind of application crashes is most likely an unhandled exception in the programs code.
This behavior is very unpleasant for the user. But no matter how hard we as developers try, there is always some unexpected exception case that we missed to handle.
With that in mind, what is the best way to deal with unexpected exceptions? How do we stop the app from crashing on unhandled exceptions?
What we need is a way to catch all unhandled exceptions in our application that we forgot to catch. But is it possible to handle .NET exceptions in global way? Not in every case. The application cant always be stopped from terminating, but there are “exceptions” from that rule.
How to track and react to exceptions on a global level
There are multiple events that are raised by the ##Framework when exceptions are thrown and/or not handled by user code. Lets take a look at them
AppDomain.CurrentDoain.UnhandledException
This event is invoked whenever there is an unhandled exception in the default AppDomain. It is invoked for exceptions on any thread that was created on that AppDomain.
This event is not invoked for exceptions that corrupt the state of the application process, like StackOverflowException or AccessViolationException.
Starting with .NET Framework 2.0, exceptions that raise the AppDomain.UnhandledException event will terminate the process. But there are a few cases where the CLR will continue running even after an unhandled exception in the AppDomain (ThreadAbortException, AppDomainUnloadedException, …).
Whenever this event is raised, our application will most likely terminate. The best we can do in this case is to capture as much information as possible about the current application state and persist it somewhere before our app will terminate.
A common reason this behavior occurs are unhandled exception on threads other then then main/dispatcher thread. To stop the application from terminating unexpectedly, it is a good idea to catch exceptions on worker threads and throw them on the dispatcher like so:
try
{
//Code that runs on another thread
} catch(Exception e)
{
Application.Current.Dispatcher.Invoke(() =>
{
ExceptionDispatchInfo.Capture(e).Throw();
});
}
This way the UnhandledException event on the Dispatcher (more info further down) will be invoked and we can decide to handle the exception, show the user an error message and close the application in a controlled way.
Notice the use of the ExceptionDispatchInfo class. Using this class to re-throw the exception will preserve the call stack of the original exception.
Dispatcher.Current.UnhandledException (WPF only)
This event is invoked whenever there is an unhandled exception on a delegate that was posted to be executed on the UI-thread (Dispatcher) of a WPF application.
The DispatcherUnhandledExceptionEventArgs class has a property called Handled. Settings this property to true will stop the application from terminating. This is not possible for unhandled exceptions on the AppDomain. Handling exceptions this way should be used with caution, because this could leave the application in an unpredictable state.
The Dispatcher.UnhandledExceptionFilter event can be used to prevent the DispatcherUnhandledException event from being invoked. It is raised before the UnhandledException event. Setting the RequestMatch property on its EventArgs will prevent the DispatcherUnhandledException event.
TaskSheduler.UnobservedTaskException
This event is raised when a faulted task, which has the exception object set, gets collected by the GC. This is useful to track exceptions in async methods where the caller forgets to await the returning task. The MSDN Docuentation for this events says:
Occurs when a faulted task’s unobserved exception is about to trigger exception escalation policy, which, by default, would terminate the process.
MSDN Documentation
But the default behavior for unobserved task exceptions was changed in .NET Framework 4.5. Unobserved task exceptions will, by default, not terminate the process anymore. They are handled by the framework after the UnobservedTaskException event is raised. This behavior can be controlled by the setting <ThrowUnobservedTaskExceptions enabled=”true” /> in the runtime section of app.config.
AppDomain.CurrentDomain.FirstChanceException
The FirstChanceException event is raised for every exception that is thrown in managed code in our applications AppDomain. Even if the exception will be handled locally. This event is useful to trace thrown exceptions but it does not really help to create a global exception handler. (MSDN)
Creating an exception handler base class
Here is an abstract class that hooks up the above event handlers and calls the abstract OnUnhandledException(..) method whenever an unhandled exception occurs in the application code.
public abstract class GlobalExceptionHandlerBase
{
public GlobalExceptionHandlerBase()
{
AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException;
Application.Current.DispatcherUnhandledException += OnDispatcherUnhandledException;
Application.Current.Dispatcher.UnhandledExceptionFilter += OnFilterDispatcherException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
}
/// <summary>
/// This methods gets invoked for every unhandled excption
/// that is raise on the application Dispatcher, the AppDomain
/// or by the GC cleaning up a faulted Task.
/// </summary>
/// <param name="e">The unhandled exception</param>
public abstract void OnUnhandledException(Exception e);
/// <summary>
/// Override this method to decide if the <see cref="OnUnhandledException(Exception)"/>
/// method should be called for the passes Dispatcher exception.
/// </summary>
/// <param name="exception">The unhandled excpetion on the applications dispatcher.</param>
/// <returns>True if the <see cref="OnUnhandledException(Exception)"/> method should
/// be called. False if not</returns>
protected virtual bool CatchDispatcherException(Exception exception) => true;
/// <summary>
/// Override this method to change the Log output of this
/// class from the Debug.WriteLine to your needs.
/// </summary>
/// <param name="msg">The message to be logged.</param>
protected virtual void Log(string msg) => Debug.WriteLine(msg);
/// <summary>
/// This method is invoked whenever there is an unhandled
/// exception on a delegate that was posted to be executed
/// on the UI-thread (Dispatcher) of a WPF application.
/// </summary>
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log($"Unhandled exception on application dispatcher: {e.Exception}");
OnUnhandledException(e.Exception);
e.Handled = true;
}
/// <summary>
/// This event is invoked whenever there is an unhandled
/// exception in the default AppDomain. It is invoked for
/// exceptions on any thread that was created on the AppDomain.
/// </summary>
private void OnAppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception exc = ExtractExceptionObject(e);
Log($"Unhandled exception on current AppDomain (IsTerminating = {e.IsTerminating}): {exc}");
OnUnhandledException(exc);
}
/// <summary>
/// This method is called when a faulted task, which has the
/// exception object set, gets collected by the GC. This is useful
/// to track Exceptions in asnync methods where the caller forgets
/// to await the returning task
/// </summary>
private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Log($"Unobserved task exception: {e.Exception}");
OnUnhandledException(e.Exception);
e.SetObserved();
}
/// <summary>
/// The method gets called for any unhandled exception on the
/// Dispatcher. When e.RequestCatch is set to true, the exception
/// is catched by the Dispatcher and the DispatcherUnhandledException
/// event will be invoked.
/// </summary>
private void OnFilterDispatcherException(object sender, DispatcherUnhandledExceptionFilterEventArgs e)
{
e.RequestCatch = CatchDispatcherException(e.Exception);
}
/// <summary>
/// This method extracts the exception instance of the AppDomains
/// <see cref="UnhandledExceptionEventArgs"/> object. If the exception
/// is not of type System.Exception, it will be wrapped in a new Exception object.
/// </summary>
private static Exception ExtractExceptionObject(UnhandledExceptionEventArgs e)
{
return e.ExceptionObject as Exception ??
new UnknownAppDomainException($"AppDomainUnhandledException: Unknown exceptionobject: {e.ExceptionObject}");
}
}
Super classes can override relevant methods to trigger their exception handling scenarios.
In the next post we will implement a WindowExceptionHandler class that shows an exception window for every unhandled exception using this base class.
The Code for this post can be found on Github.
Good work and great post…