Thursday, July 18, 2013

Unhandled exceptions in WPF applications

When it comes to unit testing there are a few areas of the application where I am comfortable not getting coverage. There are some areas of the application, typically in the UI, that are generally difficult to unit test but can be easily verified if you run the application manually. There are a few other places where testing is very difficult to validate such as the global error handler for your application. For the global error handler, you have to live with some manual testing and assume you’ve got it right.

Today I discovered one of my assumptions about the global error handler was completely wrong. My app was crashing and displaying error messages; I assumed it was crashing, logging to a file and exiting politely. It was not. And as always, I’m writing this as a reminder for you and myself.

As most know, the best place for a global exception handler is to attach an event handler to the DispatcherUnhandledException event of the application. It’s important to set the the Handled property of the UnhandledExceptionEventArgs to true to prevent the app from crashing.

However, this will only capture exceptions on the UI thread. All other exceptions will look for an event handler on that threads’ stack. If no event handler is found it will bubble up to the AppDomain’s handler. So to capture these exceptions you should add an event handler to the AppDomain’s UnhandledException event.

In contrast to the UnhandledExceptionEventArgs, UnahdledException does not have a Handled property. I assumed that the purpose of this handler was so that we could log the error and go on about our business. As it turns out, if your code reaches to this event handler it is completely unrecoverable. As in Bill Paxton, “Game over, man!” – your app is going to crash and show a nasty error dialog. The only way to prevent the dialog is to use Environment.Exit(1);

namespace MyApplication
{
    public class MyApp : Application
    {
        private static ILog Log = log4net.LogManager.GetLogger(typeof(MyApp));

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            // handle all main UI thread related exceptions
            Application.Current.DispatcherUnhandledException += DispatcherUnhandledException;

            // handle all other exceptions in background threads
            AppDomain.CurrentDomain.UnhandledException += AppDomainUnhandledException;
        }

        void DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            // prevent unhandled exception from crashing the application.
            e.Handled = true;

            Log.Fatal("An unhandled exception has reached the UI Dispatcher.", e.Exception);

            // shut down the application nicely.
            Application.Shutdown(-1);
        }

        void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = e.ExceptionObject as Exception;

            Log.Fatal("An unhandled exception has reached the AppDomain exception handler. Application will now exit.", ex);

            // This exception cannot be handled and you cannot reliably use Shutdown to gracefully shutdown.
            // The only way to suppress the CLR error dialog is to supply "1" to the exit code.
            Environment.Exit(1);
        }
    }    
}

If you want to gracefully exit the application regardless which thread created the Exception, the recommended approach is to:

  • Handle the exception on the background thread.
  • Marshal the exception to the UI thread and then re-throw it there.
  • Handle the exception in the Application.DispatcherUnhandledException handler.

There’s no easy way out here and means you need to fix the offending code. My recommendation is to use the AppDomain UnhandledException as a honey pot to find issues.

High five.

No comments:

Post a Comment