What!? A .NET Application Can Die?

by John Robbins of Wintellect

You have to love .NET. All those nasty crash problems we used to wrestle with in Win32 are now just stories we can tell around the virtual campfire. No longer will our users have to see that ugly crash dialog telling them oh so subtly that we programmers screwed up. Life it .NET is great, isn't it?

The prior paragraph was somewhat of a dream sequence. While .NET does keep us from messing with all the hassles of memory corruptions and such, as we all know, reality is a little different. As we've all seen a million times when learning .NET, your applications can certainly end sadly and abruptly. While that's fine during development, we certainly don't want to have our .NET applications die with an exception in front of our users. Nothing is more embarrassing than a confusing dialog or web page referring to unhandled exceptions.

In this column, I want to discuss how to set exception handlers in your .NET applications so you can apologize to the user for abruptly terminating and also gather more information about the unhandled exception so you can stand a chance of duplicating the problem so you can fix it. Back in the Win32 days, it was relatively easy to make a single function call, SetUnhandledExceptionFilter, so you could set a function that would get called no matter what thread caused an exception in your application. While .NET allows the same type of handling, it's got a twist in that console, WinForms, and ASP.NET applications all set up the exception handlers differently. Additionally, there's different behavior depending on which type of thread caused the exception.

After showing you the thread types, I'll discuss setting exception handling in console applications. Part of that discussion will be a small program that shows you how exceptions are reported depending on which thread type has the unhandled exception. Once past the console applications, I'll turn to WinForms applications. Finally, I'll cover ASP.NET applications.

Thread Types

As how the exception handling gets called is dependent on which thread type is causing the exception, its important that I list them. That way you'll see the ramifications when running the ConsoleException program later in the column.

  • Main Thread
    The initial thread of the console or GUI application.
  • Manual Thread
    Threads created with System.Threading.Thread.
  • Pool Thread
    Threads created with System.Threading.ThreadPool or System.Threading.Timer. Used quite a bit in the CLR
  • Finalizer Thread
    The garbage collector thread
  • Unmanaged Thread
    Win32 threads not created by the CLR.

Unhandled Exceptions in Console Applications

The .NET documentation is quite straight forward in pointing out that to handle exceptions for an Application Domain you'll set a System.UnhandledExceptionEventHandler delegate to the AppDomain.CurrentDomain.UnhandledException event like the following:


using System;

namespace SimpleEX
{
  class Class1
  {
    static void Main ( )
    {
      AppDomain.CurrentDomain.UnhandledException += 
              new UnhandledExceptionEventHandler(SimpleHandler);
              
      String x = null ;
      Console.WriteLine ( x.ToString ( ) ) ;     
    }
    
    static void SimpleHandler(Object                      Sender,
                              UnhandledExceptionEventArgs Args  )
    {
      Exception e = (Exception)Args.ExceptionObject ;
      Console.WriteLine ( "Caught : " + e.Message ) ;
    }                                   
  }
}

While setting your exception handler looks relatively straight forward, there's a couple of key "gotchas" that you need to keep in mind. The first is if you are creating other Application Domains in your code, you can only set the exception handler when executing in an Application Domain. You can't set exception handlers from outside the Application Domain.

The second issue is related to the threads I discussed earlier. If the exception occurs in the main thread, your application will terminate after your exception handler returns. However, if the exception occurs in any other thread, that particular thread will terminate, but the main thread will continue to execute as if nothing happened.

When I first ran across the fact that the main thread continues to execute, I was a little perplexed as I would have thought that the application terminated on any thread. .NET is quite a bit different from Win32, and here's yet another example of where bringing assumptions proves you wrong. If you want to treat any exception as fatal, no matter which thread it comes from, you can call the Environment.Exit method, which will end your application from inside your exception handler.

Inside your exception handler, the UnhandledExceptionEventArgs parameter has a read only property, IsTerminating, which when true, reports that the application is ending. As you'd expect you'll only see the IsTerminating set to true when the main thread has the exception. If the value is false, the thread is terminating, but not the application.

To show you exceptions in each of the managed threads, I whipped together ConsoleExceptions. This program does nothing more than let you easily play with setting an exception handler and dictating which thread type where you want to throw an exception. The command line options are as follows:

Command line options y|n m|t|p|g
You must specify if you want the error handler and which error
  y = install unhandled error handler
  n = use default error handler

  m = main thread
  t = managed thread
  p = pool thread
  g = garbage collector

If you'd like to see what happens when you have an error handler and an exception occurs in the garbage collector thread, the command line is "y g". The main thread has a finallyblock as well as a call to Console.WriteLine to tell you when the application ends. I'd encourage you to play with the various threads and how exceptions are handled in them so you'll be able to properly handle unhandled exceptions occur in your applications.

WinForms Applications

WinForms application exception handling has a twist on it. Exceptions for the main thread and the main thread only, go to a different handler. You'll need to add a delegate to the Application.ThreadException member which has the type System.Threading.ThreadExceptionEventHandler in order to handle them. If you want notification of exceptions from other threads, you'll have to also set the AppDomain.CurrentDomain.UnhandledException event as I discussed in the previous section.

Another interesting little problem is that unless you set both exception handlers before calling Application.Run, you won't be able to debug them. Consequently, you'll want to ensure you set them.

If you've written a WinForms application slightly larger than the canonical "Hello World!", you've probably seen the following dialog:

While helpful, especially when you click on the Details button to see what happened it does not make debugging any easier. What would be nicer is if you could get the Just In Time debugger to pop up instead so you could debug the problem. Fortunately Microsoft thought that might be useful. In the application configuration file, add a system.windows.forms element under the configuration element and set the jitDebugging attribute to true and now you'll get the JIT dialog instead of the normal exception.

<configuration>
    <system.windows.forms jitDebugging="true" />
</configuration>

ASP.NET Applications

Where WinForms applications are slightly different than console applications, handling exceptions in ASP.NET applications is radically different. If you want notification of errors on the page, the System.Web.UI.Page.Error event gives you the errors that occur in that class. In handling System.Web.UI.Page.Error, you'll also want to look at the Page.ErrorPage property as that String contains the page the user wants to show if there is an unhandled exception.

For handling exceptions globally to the application, the System.Web.HttpApplication class, which is the base class for Global in your GLOBAL.ASAX file, has the System.Web.HttpApplication.Error event. The System.Web.HttpApplication.Error event is only called if you do not handle the error on the page or if you re-throw the exception from your System.Web.UI.Page.Error handler.

Inside your System.Web.HttpApplication.Error handler, you can access the System.Web.HttpApplication.Server property as it contains the System.Web.HttpApplication.HttpServerUtility class which will retrieve the error through its GetLastError method. Additionally, if you'd like to keep the application running, instead of terminating, you can clear the last error with the appropriately named ClearLastError method.

Wrap Up

While .NET does make your applications much better by avoiding the memory issues and crashes we've seen in the past, you can still have numerous unhandled exceptions. However, instead of having one place to handle them all, like we did with Win32, there's a little bit of difference in .NET. While ASP.NET has its own way, WinForms and Console applications are similar. However, with the fact that unhandled exceptions terminate the main thread but not any other threads, means you'll have to pay attention to ensure your application runs as you would expect. I hope this column straightened out everything for you on handling your exceptions!

Source Code

Download: DeadDotNet.zip - 8 kb.

About the Author

John Robbins is the co-founder of Wintellect ( http://www.wintellect.com), a consulting, debugging, and education firm that helps client's ship better code faster. He is also the author of Debugging Microsoft .NET and Windows Applications (Microsoft Press) as well as the Bugslayer columnist for MSDN Magazine. Before founding Wintellect, John was an architect and product manager at NuMega Technologies for products such as BoundsChecker, TrueTime, and TrueCoverage. Prior to joining the software world, John was a Paratrooper and maroon Beret in the U. S. Army.

# # #



Comments

  • There are no comments yet. Be the first to comment!

  • You must have javascript enabled in order to post comments.

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Companies must routinely transfer files and share data to run their business, work with partners, and speed operations. However, many find the traditional approach to file transfer lacks necessary security, is too complex and difficult to manage, does not support the levels of automation needed, and breaks down when addressing the file transfer requirements of new areas like Big Data analytics and mobile applications. This QuinStreet SmartSelect discusses how the changing business environment is making the use …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds