When multi-threading applications it can be easy to lose the plot from time to time. Sometimes it can take all your energy just to remember what is running when, how to sync, lock, join, etc... Often, exception handling takes a back seat or can lose consideration with respect to where exceptions should; or will; be communicated and how they may be handled. Even if you assume you are the greatest developer who ever lived, exceptions are inevitable, and when they occur in a multi-threaded application the root cause can be very hard to isolate. In fact, depending on the type of feature being executed on a thread you may have silent failures leading to no end of rabbit-trails as dependent behaviors and/or components exhibit who knows what.
With that in mind, there are a number of patterns that can keep you out of trouble; or at least; help you isolate problems when trouble strikes. Lets tackle one of the most commonly used threading patterns first, the QueueUserWorkItem.
ThreadPool.QueueUserWorkItem(DoSomethingFeature, null);
This is something I see a lot of and unfortunately it can lead to disappointment. Any unhandled exceptions that occur in the aforementioned DoSomethingFeature() method will reach the AppDomain and will crash your application. There are; at least; two patterns we can employ to deal with this kind of problem. The first pattern focuses on catching exceptions. Thanks to lambda support, we can easily wrap our feature methods with some basic try {} catch {} blocks.
ThreadPool.QueueUserWorkItem(state =>
{
try
{
DoSomethingFeature(state);
}
catch (Exception ex)
{
//Handle the exception
}
});
The above approach will provide you an opportunity to catch unhandled exceptions but does not provide an elegant means of communicating to other threads so they can take action if needed. To achieve that, you could employ the Observer Pattern using static Events... Here is a simplified example:
Define a delegate and EventArgs implementation to communicate whatever is needed to facilitate your exception handling needs... For this sample, all we need is the Exception itself.
public delegate void CustomExceptionHandler(object sender, ExceptionArgs e);
public sealed class ExceptionArgs : EventArgs
{
public Exception Exception { get; set; }
}
Next, define a static Event in a location that is accessible to all required areas of concern.
public static event CustomExceptionHandler OnCustomException;
With that in place, we can now queue our threads as we did before, but this time we will wire up the new event/delegate created previously to communicate exception details.
ThreadPool.QueueUserWorkItem(state =>
{
try
{
DoSomethingFeature(state);
}
catch (Exception ex)
{
if (OnCustomException != null)
OnCustomException(null, new ExceptionArgs { Exception = ex });
}
});
For those layers charged with handling or responding to unhandled exceptions, they just need to subscribe to the Events.
OnCustomException += ((sender, e) => Console.WriteLine(e.Exception.Message));
Now lets address a second commonly used unhandled exception catch pattern. You may have seen code such as follows:
AppDomain.CurrentDomain.UnhandledException += ((sender, e) => /* catch and continue */));
This approach is often misunderstood... On the surface, it may appear as a method of catching an unhandled exception and preventing your application from crashing, but testing will show that this is not true starting with .NET 2.0. This delegate is provided to allow the application to save state, log exception details, etc. but will not prevent a terminal Exception from bringing down the AppDomain. Using this for the stated purposes is still a good idea, but you will need to employ other methods such as the ones above to prevent total failure.
Tags: threading, debugging, design patterns