More and more nowadays there is a push to run more application code in an asynchronous manner to prevent blocking the UI thread and making the application unresponsive. As the need for this type of programming becomes more prevalent thankfully the APIs to do so have also become easier to implement. The last part (easier APIs) has led to more and more async code sprinkled through an application like the little candies on top of donuts. Unfortunately the easier APIs also mean it’s easier to abuse the functionality by not properly implementing exception handling.
All of these async sprinkles can sink an application fast as code may be exploding left and right with the application user non the wiser. From the users perspective everything seems like it’s ok (i.e. the app doesn’t abort or show error dialogs) but nothing seems to be working.
I’d like to take a moment to look at various ways to async a task and highlight the exception handling issues related to each. To do this I am going to use the following program framework. My goal each time is to first see whether an unhandled exception occurs and if so then to handle it appropriately.
class Program { static void DoSomething(object state) { try { throw new InvalidOperationException(); } catch { Console.WriteLine("Exception thrown in method DoSomething."); throw; } } static void Main(string[] args) { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // // TODO: Insert async code that calls the method DoSomething // Console.WriteLine("Done."); Console.ReadLine(); } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("Entered UnhandledException handler."); } }
As I replace the “TODO:” with each type of async code I’ll “Start Without Debugging” in Visual Studio.
If the program runs WITHOUT encountering an unhandled exception the window will look like the following. This can happen in two different scenarios. Either the program encountered an unhandled exception but the exception was gobbled up and nobody will ever know or we have implemented proper exception handling code to deal with the exception when it occurs.
If the program encounters an unhandled exception then the window will look something like the following in which the task will then be to implement proper exception handling code.
Background Thread Exception Behavior
Before we start into the meat of this article it’s important to note both the .NET Framework version and the existence of a specific configuration setting can change the behavior of an application when an exception occurs on a background thread. For more information on this topic see http://msdn.microsoft.com/en-us/library/ms228965.aspx.
Briefly .NET 1.0 and .NET 1.1 allowed background threads to throw unhandled exceptions that WOULD NOT terminate the application. The .NET Framework would terminate the thread itself but the application would be unaffected and more than likely the unhandled exception would go unnoticed.
For all other .NET Framework version this behavior can be reinstated (BAD IDEA!) using the application configuration setting:
<configuration> <runtime> <!-- the following setting prevents the host from closing when an unhandled exception is thrown --> <legacyunhandledexceptionpolicy enabled="1" /> </runtime> </configuration>
All of the code we use following this WILL NOT have the configuration setting enabled and will be using .NET 4.5. That being said it very easy to get the same behavior without using the configuration setting on a newer .NET Framework version. In fact it sometimes seems like this old style is still in effect.
System.Threading.Thread
Our first attempt will be to use the System.Threading.Thread object. We’ll replace the // TODO: line with the code:
// 1. Thread.Start Thread thread = new Thread(new ParameterizedThreadStart(DoSomething)) { IsBackground = false }; thread.Start();
When this code is executed we see that an unhandled exception occurs. Note that I set the IsBackground property to false.
If this is set to true then no unhandled exception occurs. What’s up with this? I thought .NET Framework 2.0 and above didn’t swallow exceptions anymore? Even though “technically” the .NET Framework does not swallow exceptions anymore, “effectively” it does UNLESS you go back at some point and “sync up” with the thread. When using System.Threading.Thread this is done via a call to the Join method on the thread. So if we modify the code to:
// 1. Thread.Start Thread thread = new Thread(new ParameterizedThreadStart(DoSomething)) { IsBackground = false }; thread.Start(); thread.Join();
we see the proper behavior (that an unhandled exception occurs) whether the thread is a background thread or not. When using this method to async something the only way to properly handle the exception is in the try/catch block in the DoSomething method. Putting a try/catch block around the thread.Join() method does nothing even though with other async APIs that pattern works as we’ll see later.
delegate BeginInvoke/EndInvoke
The next attempt to async the DoSomething method will use the delegate async architecture. A delegate provides the methods Invoke(), BeginInvoke() and EndInvoke(). Using the Invoke() method would be the equivalent of just calling the DoSomething method without any async wrapping. A call to BeginInvoke() is what will cause the DoSomething method to run asynchronously. To run this code replace the // TODO: line with the code:
// 2. BeginInvoke/EndInvoke Action action = DoSomething; action.BeginInvoke(null, null, null);
We’re back to not seeing an unhandled exception. We receive the message “Exception thrown in method DoSomething.” but that’s it. No unhandled exception. Just like with System.Threading.Thread we have to “sync up” with the thread at some point in order for the unhandled exception to be properly generated:
// 2. BeginInvoke/EndInvoke Action action = DoSomething; action.BeginInvoke(null, action.EndInvoke, null);
and to properly handle the exception we can use the same method as with System.Threading.Thread and modify the catch block in the DoSomething method or we can use a try/catch block around the EndInvoke() like so:
// 2. BeginInvoke/EndInvoke Action action = DoSomething; action.BeginInvoke( null, result => { try { action.EndInvoke(result); } catch (Exception ex) { Console.WriteLine("Exception handled."); } }, null);
Note that adding a Console.WriteLine() to the catch block isn’t REALLY properly handling the exception. It’s just simulating proper handling code which would more than likely log the exception, possibly correct it and maybe even let it continue to percolate up the stack by using a throw; statement.
System.Threading.ThreadPool.QueueUserWorkItem
The next attempt to async the DoSomething method will specifically use the ThreadPool. Some of the later methods use the ThreadPool implicitly but here we will do so explicitly. To run this code replace the // TODO: line with the code:
// 3. ThreadPool.QueueUserWorkItem ThreadPool.QueueUserWorkItem(DoSomething, null);
It’s refreshing to see that we don’t actually have to do any “sync up” with the thread. And realistically you couldn’t even do it. Regardless we find that in this specific instance the unhandled exception is properly generated.
To properly handle the exception we can use a method similar to that used with BeginInvoke/EndInvoke:
// 3. ThreadPool.QueueUserWorkItem ThreadPool.QueueUserWorkItem( state => { try { DoSomething(state); } catch (Exception ex) { Console.WriteLine("Exception handled."); } }, null);
System.ComponentModel.BackgroundWorker
The next attempt to async the DoSomething method will use the BackgroundWorker class. To run this code replace the // TODO: line with the code:
// 4. BackgroundWorker BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (sender, e) => DoSomething(e); worker.RunWorkerCompleted += (sender, e) => worker.Dispose(); worker.RunWorkerAsync();
We find once again that in this initial case the unhandled exception is not properly generated. With the BackgroundWorker there is no way to “sync up” without using things like status checking loops or ManualResetEvent. We can however add code to properly handle the exception.
// 4. BackgroundWorker BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (sender, e) => DoSomething(e); worker.RunWorkerCompleted += (sender, e) => { if (e.Error != null) { Console.WriteLine("Exception handled."); } worker.Dispose(); }; worker.RunWorkerAsync();
System.Threading.Tasks.Task
The next attempt to async the DoSomething method will use the .NET 4 Task. To run this code replace the // TODO: line with the code:
// 5. Task.Factory.StartNew Task.Factory.StartNew(DoSomething, null);
Lately I’ve been finding that this is the most common pattern that somebody will use to async a method. Most likely because it’s cool and it’s easy. This particular pattern is why I wrote this article. The problem is that this pattern, like the others, will allow the method called to throw an exception and the application code will be none the wiser. Imagine 10, 20, 50, 100s of these spread throughout the code base all randomly aborting and the user is wondering why things don’t seem to work even though they see no obvious signs of problems, i.e. exceptions or error dialogs.
If we modify this code to “sync up” with the thread like so:
// 5. Task.Factory.StartNew Task task = Task.Factory.StartNew(DoSomething, null); task.Wait();
We find that the unhandled exception is properly generated, however the task.Wait() like the thread.Join() kind of defeat the purpose of attempting to run the DoSomething method asynchronously. To solve this problem use task chaining:
Task.Factory .StartNew(DoSomething, null) .ContinueWith( task => { try { task.Wait(); // If the DoSomething method returned a result // we could reference task.Result instead to // trigger the exception if one occurred otherwise // process the result. } catch (AggregateException ae) { ae.Handle( (ex) => { Console.WriteLine("Exception Handled."); return true; }); } });
In our case the DoSomething method is of type Action<object> and not Func<object, TResult>, i.e. it doesn’t return a result. If the DoSomething method returned a result we could reference task.Result instead of using task.Wait() to “sync up” with the task and trigger the exception. In this code we use the AggregateException.Handle method to handle the exceptions. The .NET 4 task returns exceptions of type AggregateException. How these differ from System.Exception is that they have an InnerExceptions (note that it’s plural) property as well as the inherited InnerException property. An AggregateException can contain multiple exceptions (one from each task that was chained) and the AggregateException.Handle method will call the specified delegate for each of those exceptions.
Since the DoSomething method doesn’t return a result there is nothing to process after it’s done so we can tell the .ContinueWith task to only execute if a fault occurred like so:
// 5. Task.Factory.StartNew Task.Factory .StartNew(DoSomething, null) .ContinueWith( task => { try { task.Wait(); } catch (AggregateException ae) { ae.Handle( (ex) => { Console.WriteLine("Exception Handled."); return true; }); } }, TaskContinuationOptions.OnlyOnFaulted);
Pretty good article, describing how difficult error handling can be with different asynchronous techniques. You should consider extending it with an async/await Task.Run example; one of the benefits of await is that it provides very natural exception handling.
BTW, your “.NET 4 Task” example is actually a “.NET 4.5 Task” example. The behavior for unobserved Task exceptions was changed in .NET 4.5 to make Tasks work better with async/await. The “silently ignore exceptions” behavior makes sense for Tasks when used in an async fashion.
Pingback: Cheatsheet: 2013 02.16 ~ 02.28 - gOODiDEA.NET