Where I talk about stuff that interests me: programming and technology

Dealing with unhandled exceptions in WPF

Just like my previous post on dealing with unhandled exceptions in WinForms, I’ll be talking about how to accomplish the same thing in WPF.  When your WPF application encounters an unhandled exception, the default .NET exception handler for WPF displays the following dialogs to the user.

There is not much information in these dialogs that would tell the user what went wrong.  More importantly there is very little information for you as the developer in these dialogs that would help you find out what went wrong.  Without stating everything I said in my previous post all over again, I’ll just say that there is a better option by replacing the default unhandled exception handler with your own.

My same disclaimer applies to this post as well.  I grabbed most of the samples your about to see from MSDN (with some tweaks).  However it was much harder to find them for WPF, and I had to combine two different sample apps to get you the complete solution for UI thread exception handling and non-UI thread exception handling.

Unlike the WinForm example, where we had to register for two events, there is only one DispatcherUnhandledException event that we have to be concerned about in WPF.  You register your handler for this in the App.xaml.

   1: <Application
   2:     x:Class="DispatcherUnhandledExceptionSample.App"
   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     StartupUri="MainWindow.xaml"
   6:     DispatcherUnhandledException="App_DispatcherUnhandledException" />

Next lets look at our code-behind for App.xaml and the handler function App_DispatcherUnhandledException().

   1: void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
   2: {
   3:     // Possible things to do:
   4:     //     Write to the system event log
   5:     //     Log to a text file
   6:     //     Take a screen shot 
   7:     //     Display a friendly message to the user about what happened and what they should do next.
   8:
   9:     // In this sample I am just dumping the error to a message box for you to see.
  10:     // THIS IS NOT A BEST PRACTICE.
  11:     var stringBuilder = new StringBuilder();
  12:     stringBuilder.AppendFormat("{0}\n", e.Exception.Message);
  13:     stringBuilder.AppendFormat(
  14:             "Exception handled on main UI thread {0}.", e.Dispatcher.Thread.ManagedThreadId);
  15:
  16:     // attempt to save data
  17:     var result = MessageBox.Show(
  18:                     "Application must exit:\n\n" + stringBuilder.ToString() + "\n\nSave before exit?",
  19:                     "app",
  20:                     MessageBoxButton.YesNo,
  21:                     MessageBoxImage.Error);
  22:     if (result == MessageBoxResult.Yes)
  23:     {
  24:         // Save data
  25:     }
  26:
  27:     // Return exit code
  28:     this.Shutdown(-1);
  29:
  30:     // Prevent default unhandled exception processing
  31:     e.Handled = true;
  32: }

There is not much required of this handler function other than the last line of code on line 31.  We set the handled property to true so that the .NET framework does not display its own handler for the exception.  If we were only concerned with exceptions that occurred on the UI thread, that would be all that is to it!

Next let’s look at how we can also handle exceptions that occur in other worker threads by using a Dispatcher.  I’ve created a simple form, similar to the previous WinForm example, that has two buttons on it – one for generating an exception on the UI thread and another that generates an exception on a worker thread.  Here are the important functions on that form:

   1: private void StartSecondaryWorkerThreadButton_Click(object sender, RoutedEventArgs e)
   2: {
   3:     // Creates and starts a secondary thread in a single threaded apartment (STA)
   4:     var thread = new Thread(this.MethodRunningOnSecondaryWorkerThread);
   5:     thread.SetApartmentState(ApartmentState.STA);
   6:     thread.IsBackground = true;
   7:     thread.Start();
   8: }
   9:
  10: private void MethodRunningOnSecondaryWorkerThread()
  11: {
  12:     try
  13:     {
  14:         WorkerMethod();
  15:     }
  16:     catch (Exception ex)
  17:     {
  18:         // Dispatch the exception back to the main UI thread. Then, reraise
  19:         // the exception on the main UI thread and handle it from the handler 
  20:         // the Application object's DispatcherUnhandledException event.
  21:         int secondaryWorkerThreadId = Thread.CurrentThread.ManagedThreadId;
  22:         Application.Current.Dispatcher.Invoke(
  23:                 DispatcherPriority.Send,
  24:                 (DispatcherOperationCallback)(arg =>
  25:                 {
  26:                     // THIS CODE RUNS BACK ON THE MAIN UI THREAD
  27:                     throw ex;
  28:                 }),
  29:                 null);
  30:
  31:         // NOTE - Application execution will only continue from this point
  32:         //        onwards if the exception was handled on the main UI thread.
  33:         //        by Application.DispatcherUnhandledException
  34:     }
  35: }
  36:
  37: private void WorkerMethod()
  38: {
  39:     // This method would do real processing on the secondary worker thread.
  40:     // For the purposes of this sample, it throws an index out of range exception
  41:     string msg = string.Format(
  42:             "Index out of range exception raised on secondary worker thread {0}.",
  43:             Dispatcher.CurrentDispatcher.Thread.ManagedThreadId);
  44:     throw new IndexOutOfRangeException(msg);
  45: }

Where all of the trickery happens is in the MethodRunningOnSecondaryWorkerThread() function.  Here we have a try catch block with a generic exception handler (code smell – I know, but there is no way around it) that wraps the execution of the WorkerMethod().  After we catch the exception, we invoke a Dispatcher to execute some code on the main UI thread.  All we do is re-throw the exception and our handler for the DispatcherUnhandledException event, in App.xaml, will catch the error.  That’s it.

You can download the full sample code here.

 

Tags: , ,

Leave a Reply