Previously, I'd describe an annoying (as opposed to fatal) bug in System.ComponentModel.BackgroundWorker that results in the UserState property of the RunWorkerCompletedEventArgs object that's passed to the RunWorkerCompleted event handler to be null regardless of what user state argument you may have passed to RunWorkerAsync.
When I'd first encountered the issue, I'd experimented with a few different work arounds, a couple of which I described in my blog post. But Chris mentioned in a comment that anonymous delegates might be useful for working around the solution - an idea I had not tried yet. My first thought was, oooo - why I didn't I think of that? Unfortunately, that idea didn't pan out.
The suggestion was that you might be able to leverage the compiler's support for capturing locals off the original callstack and making them available in the anonymous delegate that handle the RunWorkerCompleted event fired by the BackgroundWorker. The problem with that solutions is twofold:
- In the typical usage model for a BackgroundWorker component, the line of code where an anonymous delegate syntax would be used is over in a designer-managed source file (SomeForm.Designer.cs). Any mucking around you do with that event registration code would be futile - sure to be lost the next time the designer touched your form.
- Even setting aside the first issue, the operation-specific user state we'd like to pass over to the RunWorkerCompleted event isn't even available at that point. We're still just initializing the form/page/etc. It's not going to be until later on when we're ready to call RunWorkerAsync that we have our hands on the user state we'd like to make available to the RunWorkerCompleted event handler.
That said, I was still curious to see just how ugly it might be to see what an anonymous delegate-based work around would look like. The above 2 issues just meant the designer couldn't be used for registering for the RunWorkerCompleted event...
Taking into consideration the issues above, and the goal of making some arbitrary user state available in RunWorkerCompleted, the basic approach started out looking something like this:
- Right before a call to RunWorkerAsync, instantiate whatever it is that represents 'user state'.
- Register for RunWorkerCompleted, passing an anonymous delegate that captures the user state parameter and makes it available to the 'real' event handler that will do the work (assumes that this is more code than you'd want to cram into an anonymous delegate).
The above solution would look something like this:
void _someControl_Click(object sender, EventArgs ea )
{
MyUserState userState = new MyUserState(...);
_bgw.RunWorkerCompleted +=
delegate(object sender, RunWorkerCompletedEventArgs e)
{
_bgw_RunWorkerCompletedWithUserState(sender, e, userState);
};
_bgw.RunWorkerAsync(userState);
}
void _bgw_RunWorkerCompletedWithUserState( object sender, RunWorkerCompletedEventArgs e, MyUserState userState )
{
// Handle completed event, with access to desired user state.
....
}
Note that the state object I want to pass to myself (an instance of MyUserState) is setup as per normal. Then an anonymous delegate is used to capture that local variable for use later when the RunWorkerCompleted event is fired and the anonymous delegate is invoked. The anonymous delegate then just forwards that state argument along to my 'real' completed event handler (_bgw_RunWorkerCompletedWithUserState). That's easy enough.
The problem is that the above code registered for the same RunWorkerCompleted event of the same BackgroundWorker instance (_bgw) every time RunWorkerAsync is called. This is because the user state is a per-request piece of information. For the first call to RunWorkerAsync, this will work fine. But the 2nd time we call RunWorkerAsync (and the operation completes), there will be 2 RunWorkerCompleted notifications emanating from the BackgroundWorker object (one to the original event handler and one to the most recently registered handler). The 3rd call to RunWorkerAsync will result in 3 event notifications, and so on.
This is where things get ugly & this approach to the problem falls apart. The issue now is that once the RunWorkerCompleted event handler fires, we now need to unregister that event handler. The operation is complete, so the operation-specific completed event handler is no longer needed. The problem is there's no syntactical way to unregister for this event. Event unregistration is based on the 'target' that the delegate you pass to remove_someEvent (-= in C#, RemoveHandler in VB.NET) refers to. But when anonymous delegates are used, we don't have access to that information. There's simply no syntactic way to say “the same anonymous delegate that I used earlier” like there is when a named method is used.
Of course, you can work around this by keeping a reference to the anonymous delegate in your user state object, and then using that reference in your completed event handler to perform the event unregistration. But by now the code smell is pretty strong. Here's what it looks like:
void _someControl_Click(object sender, EventArgs ea )
{
MyUserState userState = new MyUserState(...);
RunWorkerCompletedEventArgs completedHandler =
delegate(object sender, RunWorkerCompletedEventArgs e)
{
_bgw_RunWorkerCompletedWithUserState(sender, e, userState);
};
userState.CompletedHandler = completedHandler;
_bgw.RunWorkerCompleted += completedHandler;
_bgw.RunWorkerAsync(userState);
}
void _bgw_RunWorkerCompletedWithUserState( object sender, RunWorkerCompletedEventArgs e, MyUserState userState )
{
// Handle completed event, with access to desired user state.
...
// Remove this completed handler, now that we're done handling this event.
//
_bgw.RunWorkerCompleted -= userState.CompletedHandler;
}
In this variation of the work around, a local variable (completedHandler) is used to capture a reference to the compiler-generated anonymous delegate. This local variable is then passed to the constructor of the fictitious MyUserState class, which simply stashes the reference in a field and makes it available via a get-only property named CompletedHandler. This local variable referring to the anonymous delegate is then used to register for the RunWorkerCompleted event just before RunWorkerAsync is called. Later, when our RunWorkerCompleted event handler runs, we can use that property to unregister for the RunWorkerCompleted event.
(Update: I'd fiddled w/the code when I copied & pasted it into the post above, resulting in the use of my userState local variable before I'd declared it.)
Blech. It works (I tried it), but it's pretty ugly. That approach made me feel strongly like I was trying to fit a square peg into a round hole. After sorting through this, and the other work arounds I had previously experimented with, I arrived at a (subjectively, I know) cleaner approach that I think I'll use in my own code.
My solution is still to avoid using the designer to register for RunWorkerCompleted on my behalf, since it won't be until later when I'm prepared to call RunWorkerAsync that I'll have my hands on the state I'd like to make available to both my DoWork event handler as well as my RunWorkerCompleted event handler. But I don't use anonymous delegates at all. Instead, I use a generic event handler helper class that serves as a go between between the BackgroundWorker object and my intended event handler object. Like the anonymous delegate, this little helper class stashes the user state I need later, but it also takes care of registering & unregistering for the RunWorkerCompleted event handler on a per-request basis. This cleans up the code quite a bit.
Here's what the helper class looks like:
public class BackgroundWorkerCompletedHandler<T>
{
BackgroundWorker _bgw;
RunWorkerCompletedEventHandler _completedHandler;
T _userState;
object _sender;
public BackgroundWorkerCompletedHandler(BackgroundWorker bgw, RunWorkerCompletedEventHandler completedHandler, T userState)
{
_bgw = bgw;
_userState = userState;
_completedHandler = completedHandler;
_sender = null;
_bgw.RunWorkerCompleted += this.OnRunWorkerCompleted;
}
void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs eventArgs)
{
_sender = sender;
try {
_completedHandler(this, eventArgs);
}
finally {
_bgw.RunWorkerCompleted -= this.OnRunWorkerCompleted;
}
}
public BackgroundWorker BackgroundWorker { get { return _bgw; } }
public T UserState { get { return _userState; } }
public object Sender { get { return _sender; } }
}
(Update: Another copy/paste error - had dropped the generic <T> declaration on the class along the way.)
And here's the client code rewritten to take adantage of it:
void _someControl_Click(object sender, EventArgs ea )
{
MyUserState userState = new MyUserState(...);
BackgroundWorkerCompletedHandler completedHandler = new BackgroundWorkerCompletedHandler(_bgw, _bgw_RunWorkerCompleted, userState);
_bgw.RunWorkerAsync(completedHandler);
}
void _bgw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
BackgroundWorkerCompletedHandler completedHandler = (BackgroundWorkerCompletedHandler)sender;
// Handle completed event, with access to everything needed:
// completedHandler.BackGroundWorker => the BackgroundWorker instance that just completed the operation
// completedHandler.UserState => a typed reference to MyUserState
// completedHandler.Sender => the original 'sender' of this event notification (BackgroundWorker or whatever it happens to be)
....
}
With this approach, I just need to instantiate the helper object, telling it which BackgroundWorker's RunWorkerCompleted event to register for (_bgw), where to forward the RunWorkerCompleted event notification to (_bgw_RunWorkerCompleted), and what user state to make available to that handler (userState). The BackgroundWorkerCompletedHandler then internally takes care of registering & unregistering for the RunWorkerCompleted event handler for me.
I realize it's a subjective thing, but in all of the variations I implemented, this approach seems the cleanest & most straight forward to me. You can find a complete working sample (tested on the 2.0 RC) here, along with detailed comments throughout the code documenting what's going on. The sample is a Windows Forms application that uses a BackgroundWorker to interact with a very slow Fibonacci sequence generator on a background thread. The sample demonstrates how to use the BackgroundWorkerCompletedHandler presented here, as well as how to use the ReportProgress method & ProgressChanged event to safely update the UI elements on the form in the correct context.
Posted
Oct 24 2005, 09:40 AM
by
mike-woodring