Moby Disk Consulting
Software Development, Training & Consulting
William Garrison - mobydisk at mobydisk daht com

Throwing .NET Exceptions Through Message Loops

There are a few complexities that can occur if you throw an exception from a Windows Form and attempt to catch it inside another Windows form, or in the main program. This exists in .NET 1.x through 2.0. Here I demonstrate an example of the problem and propose a solution that allows each form to decide what exceptions are propogated to the parent, and which are not.

Before reading this article, you should be familiar with using the UnhandledException or ThreadException events. There are a number of good articles on the subject.

The Problem

Suppose you have an application with a typical ThreadException handler that displays a message box informing the user of the error.

   public class Program
   {
      static void Main() 
      {
         Application.ThreadException +=new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
         Application.Run(new FormMain());
      }

      private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
      {
         MessageBox.Show(e.Exception.ToString(), "ThreadException");
      }
   }

Now suppose that this application contains a form with a single button. When the user clicks the button, some work is done:

   public class FormMain : System.Windows.Forms.Form
   {
      public FormMain()
      {
         InitializeComponent();
      }

      private System.Windows.Forms.Button btnWorker;
      private void btnWorker_Click(object sender, System.EventArgs e)
      {
         try
         {
            new Worker().DoWork();
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.ToString(),"Worker encountered exception!");
         }
      }

      [+] Windows Form Designer generated code
   }

We would expect any exceptions thrown by the Worker.DoWork() method to be caught in the catch block of btnWorker_Click(). However, if the DoWork() method contains a message loop then this may not happen. Suppose the worker looks something like this:

   public class Worker : System.Windows.Forms.Form
   {
      public Worker()
      {
         InitializeComponent();
      }

      public void DoWork()
      {
         ShowDialog();
      }

      private System.Windows.Forms.Button btnDoWork;
      private void btnDoWork_Click(object sender, System.EventArgs e)
      {
         throw new ApplicationException("Worker encountered error!");
      }

      [+] Windows Form Designer generated code
   }

The worker class involves a user-interface. When we click on the "Do Work" button, the worker throws an exception. But the exception never arrives in our catch block! Instead, it is caught by the ThreadException handler:

This is because any exceptions that propogate up to a message loop will be handled by the ThreadException handler. If there is no thread exception handler then the default handler will display a dialog box to the user with a "Details" button. Both ShowDialog() and Application.Run() will both create message loops. But in this case, we did not want the ThreadException handler to fire. So we must temporarily disable it during the DoWork() call:

      public static bool exceptionHandlerEnabled = true;
			
      private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
      {
         if (exceptionHandlerEnabled)
            MessageBox.Show(e.Exception.ToString(), "Error from " + source);
         else
            throw e;
      }

Then, the worker object must disable the exception handler for the duration of the ShowDialog() call:

      public void DoWork()
      {
         bool saveExceptionHandler = Program.exceptionHandlerEnabled;
         try
         {
            Program.exceptionHandlerEnabled = false;
            ShowDialog();
         }
         finally
         {
            Program.exceptionHandlerEnabled = saveExceptionHandler;
         }
      }

This time when the exception is caught by the ThreadException handler it is silently re-thrown. The re-thrown exception is then caught by our catch block as expected:

Overall, this allows you to turn on and off the unhandled exception handler for each form. There are a few down-sides to this: It is not thread-aware, although that could be fixed. The call stack is also lost when the exception is re-thrown, so it might be better to wrap the exception in a new one so that the original call stack is preserved on the innerException.