I've been tinkering with the development of a BackgroundWorker knock off that supports queued semantics for its RunWorkerAsync method (the BackgroundWorker, like System.Net.WebClient, only supports a single async operation at a time). A QueuedBackgroundWorker would support overlapping calls to its RunWorkerAsync method, handling each request in FIFO order. I'll blog here once I clean it up and post it to my sample code page. At any rate, one of the things I was trying to do was to make it easy to swap a QueuedBackgroundWorker into a piece of code that was already written against the stock BackgroundWorker.
Towards that end, I wanted to define my ProgressChanged and RunWorkerCompleted events so that they used the same delegate types that BackgroundWorker uses (ProgressChangedEventHandler and RunWorkerCompletedEventHandler). This meant sticking to the stock event argument classes those delegates define in their parameter lists (ProgressChangedEventArgs and RunWorkerCompletedEventArgs). But if I stuck with the use of RunWorkerCompletedEventArgs, I was going to have to be content the problem of its UserState property always being null, forcing users of my QueuedBackgroundWorker class to work around that bug in some fashion.
So my choices were to either stick with the use of the stock RunWorkerCompletedEventArgs structure (and its bug), or define my own RunWorkerCompletedEventArgsWithUserState class along with a corresponding RunWorkerCompletedWithUserStateEventHandler delegate.
The really frustrating thing about that bug has been that RunWorkerCompletedEventArgs, which doesn't allow callers of its constructor to pass in a user state reference, derives from AsyncCompletedEventArgs, which does allow such a reference to be passed to the constructor. Whatever is passed to that constructor is then made available via the UserState property exposed by AsyncCompletedEventArgs and inherited by RunWorkerCompletedEventArgs. Here's what those two constructors look like (courtesy of Reflector):
public AsyncCompletedEventArgs(Exception error, bool cancelled, object userState)
{
this.error = error;
this.cancelled = cancelled;
this.userState = userState;
}
public RunWorkerCompletedEventArgs(object result, Exception error, bool cancelled)
: base(error, cancelled, null)
{
this.result = result;
}
Notice the hard coded null that RunWorkerCompletedEventArgs passes to AsyncCompletedEventArgs for the 3rd parameter (userState). My original thought was to define a RunWorkerCompletedEventArgsWithUserState class that used reflection to set the private userState field inherited from AsyncCompletedEventArgs. Of course, using reflection against non-public members of types whose implementation you have no control over means you're way off the beaten path of supported programming activities. I've done that plenty in the past, but was wondering if there was a better way.
That's when it occurred to me that although RunWorkerCompletedEventArgs's constructor calls the AsyncCompletedEventArgs constructor 'wrong', I could maybe call it 'right'. Here's what I ended up with:
public class RunWorkerCompletedEventArgsWithUserState : RunWorkerCompletedEventArgs
{
public RunWorkerCompletedEventArgsWithUserState(object result, Exception error, bool cancelled, object userState)
: base(result, error, cancelled)
{
Type[] ctorArgs = { typeof(Exception), typeof(bool), typeof(object) };
ConstructorInfo ctor = typeof(AsyncCompletedEventArgs).GetConstructor(ctorArgs);
ctor.Invoke(this, new object[] {error, cancelled, userState});
}
}
Here, the RunWorkerCompletedEventArgsWithUserState constructor extends the stock RunWorkerCompletedEventArgs class. Three of the four arguments passed to my constructor are then simply forwarded along to that base class constructor, after which everything is initialized except for the userState field inherited from AsyncCompletedEventArgs. So after calling the base class constructor, I use reflection to call the public constructor of AsyncCompletedEventArgs a second time, passing in the missing userState argument.
Because RunWorkerCompletedEventArgsWithUserState extends RunWorkerCompletedEventArgs, I can pass a reference to RunWorkerCompletedEventArgsWithUserState wherever a reference to RunWorkerCompletedEventArgs is expected. This means I can define my RunWorkerCompleted event in terms of the stock RunWorkerCompletedEventHandler delegate, making it easier to swap my QueuedBackgroundWorker into code that was previously using BackgroundWorker. And because my constructor patches up the uninitialized userState field inherited from AsyncCompletedEventArgs, the UserState property that's also inherited from AsyncCompletedEventArgs will now faithfully return whatever user state argument the caller originally passed to RunWorkerAsync.
Disclaimer
Obviously, I'm still playing fast & loose with things. Yes, I've avoided using reflection against non-public members to accomplish my goal. But now I'm using ConstructorInfo in a way it wasn't intended to be used. The AsyncCompletedEventArgs constructor is now being invoked twice for the same instance. Had that constructor been doing anything more complex than just writing parameter values directly into private fields, this could cause problems. So in reality, I've still got code that's dependent on the internal implementation of a class I have no control over.
So this post should not be interpreted as best practice advice. It's more of a “hey, look what I can do” piece of trivia :-) It was a fun little piece of code to write. And it'll continue to work in all liklihood for a really long time. But for production usage, you should probably avoid doing something like this. For that reason, the QueuedBackgroundWorker sample I'll post later shows how to solve the problem the right way, without bending any rules.
Posted
Oct 25 2005, 01:10 PM
by
mike-woodring