Roy Osherove

View Original

Creating a better BackgroundWorker: CancelImmediately and other goodies

One of the nice new features you get in .NET 2.0 Winforms is the new BackgroundWorker component. It allows safely executing long tasks in a different thread than the GUI, while allowing an easy event-driven interface to perform the task and respond to events (such as detecting work progress). There are even versions of this for .NET 1.1 courtesy of Juval Lowy.
 
However, one of the things that to me still are missing with this components is support for one important key scenario. Right now the (only) scenario supported by this component is the one where the long running task to be executed is made of a series of short-term actions one after the other. For example, calling a web service multiple times, calculating stuff, getting things from the database etc.. This scenario is based on the fact that each small part of the long-running task takes a small amount of time, so that after that part is done, the code can check a “Cancel” argument on the component to see of the user wishes to cancel the task. That means that from the moment I press “cancel” on a button, nothing really happens except a flag is set, and it’s up to the application’s code to cancel; as soon as possible. That might be  half a second, it might be 10 seconds.
 
It’s the 10 seconds thing that is problematic. Suppose most of the work in the long running task was made up of little tasks, each taking a long time? It will take a long amount of time for the application’s code to notice that a Cancel was requested by the user. In fact, it may notice a cancel request only after it finishes it’s task.
 
Right now BackgroundWorker has only one way to cancel: “CancelAsync()”. The feature I was missing was “CancelImmediately()”.  I’ve even gotten to talk a little with Mark Rideout, Program Manager for the .NET Client who supported my claim that this is not possible with the current version of BackgroundWorker.
 
“Roy – The BackgroundWorker gets its thread from the thread pool. As such, it doesn’t manage the creation or termination of the thread nor does it expose the thread that it is using. After the BackgroundWorker completes its processing the thread is released back to the “pool” and will be terminated by the thread pool when it is no longer needed. The BackgroundWorker actually doesn’t know what thread it is running on.
 
Regarding your case where you need to cancel a long running query that you don’t have control over – the BackgroundWorker doesn’t help in this case since it doesn’t know its thread. You’ll need to resort to manually creating a thread and terminating the thread. “
 
Which is exactly what I’ve always had to do until now, and I didn’t want to do it again. Take The Regulator for example. It’s possible to create a Regular Expression that will run for minutes or hours and hang, a perfect example of a single blocking task that the current BW is not built for, so running it on the same thread as the GUI when testing Regex is out of the question. You *have* to use a different thread. I wanted to be able to do the same thing using BW.
 
I’ve created two classes:
  1. ExtendedBackgroundWorker - this is the one most of you will want to use
  2. BackgroundWorkerEx - This is the one some of you might want to try if you still have some features missing
Both classes are available for download in VB or C# sources. Before I get on with all the technical stuff of what it took to add this functionality and why the hell did I create two classes, download them.
 
Download for VB.NET
Download for C#
 
 
Technical babble
 
The ExtendedBackgroundWorker class  - CancelImmediately and its consequences
My solution was to create a class that inherits from BackgroundWorker, and overrides the “OnDoWork” method. Since the OnDoWork method is already executing on a Thread Pool Thread, I was able to save the thread to a local variable and have the ability to call “Abort” on the thread at a user’s request.
 
Of course, this has consequences. Calling thread.Abort throws a “ThreadAbortException” which, no matter how many times you catch it, will always propagate to all the callers.  There is one solution to this, which worked for me in this specific scenario: Calling Thread.ResetAbort() resets the abort state and the exception is effectively “swallowed” for all the callers. That also means that the Thread actually keeps running, only this time it’s running at a point after the long running task has executed and aborted, effectively finishing its job (sort of like a crude “goto” statement for Multi Threading). That’s the solution I used in the component, but there is still a scenario where a user of this component will get the exception:
 
- if the code inside the “DoWork” event catches an “Exception”, a ThreadAbortException will also be caught. In that case, that code should have two catch clauses: one for ThreadAbortException which does nothing, simply ignores it and returns, and the other for any other exception.
- if the code inside the “DoWork” event does not catch any exception, no specific action needs to be taken. The ThreadAbortException will be caught by the component and all will be well in the world once again.
 
The component also takes care of calling the “Completed” event in case of an immediate cancellation,. It uses reflection to bring in the local members pertaining to the async operation and invokes them accordingly.
 
The BackgroundWorkerEx class - Support fast Progress Reports
One thing which is *not* solved by my class is a limitation of the BackgroundWorker, and essentially a “bug”. If you loop quickly inside the “DoWork” method and simply call “ReportProgress” on each loop fast enough, you’ll get a thread hang. however, calling Thread.Sleep(10) before each loop fixes this problem.
Mark Rideout also responded about this:
 
“flooding the GUI thread with progress keeps the GUI thread from being able to process paint and other windows
messages, so the Sleep call is the correct way to deal with this. We have documented this as well in the topic titled “How to: Run an operation in the background” http://msdn2.microsoft.com/en-us/library/hybbz6ke.aspx ” [the link seems broken, see the cached version]
 
I wanted to try and implement this solution directly inside my new class (called ExtendedBackgroundWorker), but it was impossible to do, mainly because some of the methods I wanted to override were not virtual. So, using Reflector and the Decompiler addin for Reflector, I recreated the source for BackgroundWorker in C# and VB, and implemented this functionality myself, including the “CancelImmediately”  feature.
So, with the “BackgroundWorkerEx” component, you get both features - support for fast progress reports *and* immediate cancellations ability. Most applications won’t need this one, but the simpler one, that inherits from BW, since most scenarios have plenty of time between ReportProgress Calls (If any), but it was a nice exercise just in case and it allowed me to study a new construct available in .NET 2.0, called the “AsyncOpreation” class. I’ll be doing a short article on that one later this week hopefully. It’s pretty darn cool.
 
Hope you like this and that these classes help you in your efforts!