~ Ready? ~
Proper error handling is necessary for any application. When you consider various solutions for a problem, don’t forget to consider error handling as well! All too often I have seen developers use an inappropriate solution because it was easier in the “success” case. Just as one example: the last few years I’ve seen many developers use
ThreadPool.QueueUserWorkItem for background operations; after all, (they think), it’s really simple - I can just toss a delegate onto the thread pool! It is true that for the “success” case it’s easier to use
BackgroundWorker, but what about for the “failure” case? What happens when the delegate throws an exception? Hint: it’s not pretty, and the code they have to write to catch the exception and marshal it to another thread is way more complex than the same code using
So, the lesson here is that you need to consider error handling when looking at options. We’ll look at optional characteristics later in this series (cancellation, progress reporting, etc), but proper error handling is not optional; it is required.
~ Fight! ~
DoWork event can throw exceptions, which are automatically caught and placed on the
Error property of the arguments passed to
RunWorkerCompleted. The code is not too bad:
Task.Run will also capture any exceptions and place them on the returned
Task. When the task is awaited, the exceptions are propagated. This means that you can use the normal try/catch blocks to handle exceptions:
Personally, I prefer the try/catch system because it is more familiar to developers than
RunWorkerCompletedEventArgs. Also, it’s easy to overlook the exception in
BackgroundWorker.RunWorkerCompleted; there’s no possible way to overlook an exception thrown by
Let’s make the example a little more realistic. Instead of displaying the exception to the user, let’s allow it to propagate through the continuation. This is a realistic way to handle unexpected exceptions at this level in the code.
There’s a pretty good “gotcha” when propagating exceptions. If you just re-throw them, then you lose the original stack trace. .NET 4.5 introduced the
ExceptionDispatchInfo type which can preserve the original stack trace; you just have to remember to use it.
await will correctly preserve the stack trace for propagated exceptions, the
Task.Run code is quite simple:
Whether handling the exception immediately, or propagating the exception, the
Task.Run code is cleaner and less error-prone than the