Safely running background threads in ASP.NET 2.0#
The .NET Framework 2.0 no longer allows background threads to die silently when an unhandled exception is thrown. I'm not going to dive into the details of the change, as it is already well-documented on the web (this post describes the behavior for different application types & Scott Allen focuses on the ASP.NET impact).

The impact is more significant for ASP.NET applications
I think this is actually a bigger change for ASP.NET apps, because it has always had a "safety net". In console/WinForms applications, if there is an unhandled exception on the primary thread, the application dies. Having your application die now because of an unhandled exception on a background thread is not that big of a difference. You have an unhandled exception in your application, the application dies. Consistent.

However, in ASP.NET, exceptions that you did not handle in your own code on the primary thread were always caught by a default handler (affectionately known as the "yellow screen of death"). Your application did not die. You could add a handler for the HttpApplication.Error event (Application_Error in global.asax) to centralize your exception handling and logging logic. In 1.x, exceptions that occurred on a background thread would die silently. Your application did not die. Consistent. But now, in 2.0, exceptions on your primary thread still do not kill your application, and can be logged by your global handler. But exceptions on your background threads will kill your application, and will not be logged.

My approach to exception handling and how it is now more difficult to implement
My philosophy is to never catch exceptions unless there is something I can do to address the issue. I prefer not to catch all possible exceptions throughout the internals of my code, and instead rely on try...finally blocks to clean up in case of an exception, and allow the exception to bubble up to a top-level handler where it can be logged (of course I catch exceptions when there is something the code can do to resolve the issue or continue in a known state).

The new background thread exception behavior forces me to create "top level handler and logging" code for the application (global.asax) AND within every method that I run on a background thread. I end up with a lot of duplicate code, and explicit exception handling and logging that I would prefer to be hidden behind the scenes instead of cluttering up my application logic.

A proposed solution
My goals:
  • Allow unexpected exceptions on all threads to be logged by a top level exception handler
  • Do not let the application die because of an exception, no matter which thread it is running on (at least not before I get a chance to log it).
  • Do not clutter the application logic with repeated try..catch and logging code.
To satisfy the top level exception handler goal, I would like to make use of the existing HttpApplication.Error / Application_Error mechanism. That means I need to somehow transfer the exception that occurred on a background thread to a primary ASP.NET thread. To satisfy the "do not die" and "do not clutter" goals, I need to wrap all calls to background thread methods with a single piece of code.

I've created the SafeWaitCallback class. It's Call method is used in place of a WaitCallback delegate. For example, if I want to execute the method DoTask on a background thread, I would call it like this:

System.Threading.ThreadPool.QueueUserWorkItem(new SafeWaitCallback().Call(DoTask));

SafeWaitCallback does two things: it wraps calls to the target method in a try...catch block, and it forwards the caught exception to a custom HttpHandler. It forwards the exception by serializing it to a byte array, and then POSTing it to the handler, which will receive the bytes and deserialize back to an Exception object.

using System;
using System.Net;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

public class SafeWaitCallback {
  public static Uri ApplicationUri;
  System.Threading.WaitCallback callback;

  public System.Threading.WaitCallback Call(System.Threading.WaitCallback callback) {
    this.callback = callback;
    return CallbackWrapper;
  }

  private void CallbackWrapper(object state) {
    try
    {
      callback(state);
    }
    catch (Exception e)
    {
      byte[] exceptionData;

      MemoryStream stream = new MemoryStream();
      BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Persistence));
      formatter.Serialize(stream, e);
      exceptionData = stream.ToArray();

      WebClient client = new WebClient();
      Uri handler = new Uri(ApplicationUri, "TransferException.axd");
      try
      {
        client.UploadData(handler, exceptionData);
      }
      catch (WebException) { }
    }
  }
}

By forwarding the exception to the custom HttpHandler, I can re-raise the exception, and since the handler will be running on a primary ASP.NET thread (not in the background), the exception will be caught by my global handler, and logged just like any other exception.

using System;
using System.Web;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

public class TransferredExceptionHandler : IHttpHandler {
  public bool IsReusable { get { return true; }}

  public void ProcessRequest(HttpContext context) {
    byte[] exceptionData = new byte[context.Request.ContentLength];
    context.Request.InputStream.Read(exceptionData, 0, exceptionData.Length);

    Exception transferredException;
    MemoryStream stream = new MemoryStream(exceptionData);
    BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Persistence));
    transferredException = (Exception)formatter.Deserialize(stream);
    throw new Exception("[Background exception transferred - see InnerException]", transferredException);
  }
}

The handler is registered in web.config with the following entry:
      <httpHandlers>
        <add verb="POST" path="TransferException.axd" type="TransferredExceptionHandler" />
      </httpHandlers>

Let me know what you think
I've attached the full source code in the form of a Web Site project. It includes a Default.aspx page which demonstrates the different scenarios of an unhandled exception (primary thread, background thread, "safe" background thread using my new code).

This is not production ready code, it is still in the proof of concept stage. For one, it probably needs better exception handling within the CallbackWrapper exception handler (what if the serialization blows up?). More importantly, I'm not yet convinced this whole idea is a worthwhile approach. It meets my goals, but it feels a little hackish. I wanted to see if it was possible, and then throw it out there to get some feedback. Anyone see any major flaws in the concept? Even better, does anyone know of a more elegant approach to achieve the same goals?

Download AspNetBackgroundExceptions.zip (2.55 KB)

Wednesday, June 21, 2006 9:50:02 PM (Central Daylight Time, UTC-05:00) #    Comments [3]  | 

 

Friday, June 23, 2006 1:16:02 AM (Central Daylight Time, UTC-05:00)
Awesome! I like the idea a lot (and have blogged on it). A couple thoughts occur to me. First, do we capture enough details in the serialized exception? Maybe we should wrap another specific exception type around the actual caught exception with details about the thread where it really occured... I'm going to play some tomorrow. Second, maybe you should be doing a POST instead of a GET to insure we've got enough message-size available.
Friday, June 23, 2006 8:18:38 PM (Central Daylight Time, UTC-05:00)
Thanks for the feedback Marc (and nice words on your blog http://musingmarc.blogspot.com/2006/06/more-about-exception-handling-and-new_23.html).

I am doing a POST, and not a GET. In fact, the handler is specifically registered only for the POST method - a GET attempt will give you a 404. I want to make it hard for people to call the handler directly. Along those lines, I'd like to add some code so that the handler only proceeds if the request is coming from the localhost. You don't want outsiders posting junk to your logs.

I originally planned to make a custom wrapper exception type (BackgroundThreadException?) in order to gather more details, but when it came down to it, I couldn't find a use for it. I was going to capture the thread name, but then realized that since I'm using a ThreadPool thread, the thread identity really doesn't mean anything (as the thread can be reused for multiple tasks). Of course, you could expand on the library to add wrappers for ThreadStart and ParameterizedThreadStart delegates, in which case knowing the thread identity could become more useful.

Let me know if there are additional details you can think of that would be useful to capture. I appreciate the feedback in fleshing this thing out into a useful tool.
Friday, January 25, 2008 3:22:56 AM (Central Standard Time, UTC-06:00)
Hi. I liked the idea. Iim trying it out but I get 401 Authorize error when I call the uploaddata method on the webclient. Is there any config on IIS that has to be done?
Comments are closed.
All content © 2010, josh
About this site
Send mail to the author(s) Contact me
Feed your aggregator (RSS 2.0)
Joshua Flanagan
I am a software developer focused on continuous improvement in the .NET community
Los Techies

On this page
Archives
Rest of the world

Acknowledgements

Powered by: newtelligence dasBlog 2.1.8209.14743

Special thanks to LosTechies.com

Site theme based on the essence design by Jelle Druyts

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.