UI Exception Handling vs. AppDomain Exceptions
I'm building an uber-exception handling system for all of our apps at work (basically handle unexpected exceptions and post them to out bug tracker, JIRA) and wanted to clear up some confusion on the differences between unhandled exceptions. As an FYI, this information is just for WinForm apps.
By default if you create a new WinForm app any unhandled exceptions are tossed into a dialog box like this:
Perhaps while you're debugging you've seen this:
That's the built-in exception assistant Visual Studio provides. It kicks in when running your app from inside the IDE and lets you inspect your system. At this point you're basically screwed and something terrible has happened, so this is your last chance to maybe see what went wrong.
The exception assistant is useful as you can edit your code on the fly, crack open the exception (and investigate other values), or just continue along your merry way. If the exception assistant really irks you, you can go into the Debugger options for visual studio and disable it. When you do this, you'll get a dialog that looks like this:
Not as descriptive as the exception assistant, but more intuitive if you just want to motor along (say to your own handler which is what we'll do).
Let's setup an unhandled exception catcher. Here's our main code before we add the handler:
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); }
Now my kung-fu design skillz kick in and we'll build a highly sophisticated UI to drive our exception handler. Behold the mighty user interface to end all user interfaces:
To create a handler we create a method and attach it to the ThreadException event handler. In our app we'll throw some exception and let the system handle it. Here's the updated code:
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException += Application_ThreadException; Application.Run(new Form1()); } static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { MessageBox.Show("Something terrible has happened."); }
We've tied into the ThreadException handler on the Application class with our own method that will dump the exception to a simple dialog box:
However in the AppDomain class there's an UnhandledException handler that you can tie into, just like we did with the ThreadException on the Application class above. The ThreadException is for dealing with exceptions thrown on that thread (and in this case, its our main form) but the AppDomain handler is for *any* unhandled exception thrown (for example, a SOAP call to a web service). So we should hook into that one as well like so:
[STAThread] static void Main() { ... AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; ... } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { MessageBox.Show("Something else has happened."); }
You might also notice the signature is different. Rather then getting a ThreadExceptionEventArgs object we get an UnhandledExceptionEventArgs one. The differences are subtle:
- ThreadExceptionEventArgs contains a property of type Exception that is the exception that was thrown
- UnhandledExceptionEventArgs contains a property called ExceptionObject which is the exception thrown (except that it's of type object rather than an Exception)
- UnhandledExceptionEventArgs also contains a bool property called IsTerminating which tells you if the CLR is about to shut down
The question is, does that make our ThreadException handler obsolete? Not really but there's different behaviour around the exception handler based on another setting. In the Application class there's a method called SetUnhandledExceptionMode which lets you control how thread exceptions are handled:
[STAThread] static void Main() { ... Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); ...
}
Adding this to your setup can result in two different behaviours:
- If you set it to ThrowException, then your CurrentDomain_UnhandledException method is called (even before the Visual Studio IDE gets ahold of it) and the Application_ThreadException method is never called.
- If you set it to CatchException, then your Application_ThreadException method is called. If you're running inside Visual Studio, the IDE steps in before this with it's own exception handler first then control is passed onto your method if you continue with execution.
One little extra note, if you choose option #1 and call SetUnhandledExceptionMode to ThrowException your AppDomain handler gets called but running outside the IDE you'll get this lovely dialog box:
Your app generally shouldn't report information to Microsoft (unless you're really important), but this is what happens when running your app in normal user mode rather than developer mode. I'm not sure if there's a way to prevent having this bad boy popup on you when you hook into the AppDomain event handler (feel free to chime in on the blogs comments if you know how).
Hope that clears up a little on how exceptions work and the different behaviours you can get out of them. Happy exception handling!