In my previous post, I talked about the need to be able run a piece of work in WPF on a background thread, leaving a responsive UI in the process. I am going to start walking through how to create a reusable class that will easily allow a method to be run in the background.
The first thing I want to do is figure out how I want to use this class. I’d like to be able to pass my class a method that may or may not take a parameter to kick off the process, and may or may not return a value. Once the work has been completed, I’d like my class to call another method that also may or may not have a parameter, possibly the result of the previous method . Finally, I want to be able to handle exceptions in my code gracefully.
Let’s discuss the first requirement. This seems to fit nicely with some basics that are built into .NET. The first is the Action object. This is a Delegate for a method that has no parameters and no return value. Luckily, Action(T) is an alternate implementation of a Delegate, that allows a method that takes one parameter, T, and returns no parameters. So we have the delegates for methods that return no values. But what about methods that return values? Enter Func(TResult). This Delegate requires a method that has no parameters and returns one value, of type T. And you guessed it, there is an alternate implementation, Func(T, TResult), that allows one parameter and one return value. The combination of all of these should cover the starting method.
The second requirement is a little simpler. We want to call a method when the code is completed that may or may not have a parameter. Well, we already covered Action and Action(T) which fits perfect for this. The same goes for the exception handling, except in this case we know we are going to have an Action(Excetion), since we will presumably be handing an System.Exception.
So, looking back on the requirements, you may have noticed they all have something in common. Each of the delegates inherit from Delegate. Great! This means our references to these methods are general and only store instances of Delegate, eliminating the need for a Generic class! We can now just call Invoke on the delegate.
I’m a big fan of the factory pattern, so I’ve made a decision that my constructors should all be private, forcing the person consuming our class to call some factory methods to create my class.
Because it would take longer and we would be covering the same information, we’re not going to go through each of the cases specified above, but instead show only examples from the most complicated case. We want to run some background work on a method that takes one parameter, returns one value, and then call a method that takes that returned value as a parameter. Let’s start with the signature of the factory method.
public static BackgroundMethod Create<T, U>(Func<U, T> entryFunc, Action<T> callbackAction, Action<Exception> errorAction, T parameter)
{
return new BackgroundMethod();
}
Should be pretty straight forward considering what we discussed. This takes three methods with various signatures, as well as the parameter for the entryMethod. We added some generic parameters here to make sure that all of the types will match when the methods are called.
Based on what we need, our class will need to store some local values. First, it will need to store all of the values passed into our method so we can access them later. Second, we need the work horse, the BackgroundWorker class.
#region Fields
BackgroundWorker _worker;
Action<Exception> _errorAction;
Delegate _entryDelegate;
Delegate _callbackDelegate;
object _parameter;
#endregion
Remember, the two Delegate declarations are storing the Func and Action since they both derive from Delegate.
Now, at construction of the class, we will want to new up our BackgroundWorker and subscribe to the events on it. As previously discussed, we will want the DoWork and RunWorkerCompleted events, as well as to allow the BackgroundWorker to be cancelled. And, to make it easier for later, we’ll add a couple overloads of our constructor.
#region Construction
private BackgroundMethod()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += DoWorkHandler;
_worker.RunWorkerCompleted += RunWorkerCompletedHandler;
}
private BackgroundMethod(Delegate entryDelegate, Delegate callbackDelegate, Action<Exception> errorAction)
: this(entryDelegate, callbackDelegate, errorAction, null)
{
}
private BackgroundMethod(Delegate entryDelegate, Delegate callbackDelegate, Action<Exception> errorAction, object parameter)
: this()
{
_entryDelegate = entryDelegate;
_callbackDelegate = callbackDelegate;
_errorAction = errorAction;
_parameter = parameter;
}
#endregion
We’re making use of our original constructor in our overloads by calling this(), as well as setting some of our local values. Let’s update our static methods as well now to use our new constructor.
public static BackgroundMethod Create<T, U>(Func<U, T> entryFunc, Action<T> callbackAction, Action<Exception> errorAction, T parameter)
{
return new BackgroundMethod(entryFunc, callbackAction, errorAction, parameter);
}
We have all the information to do the work now, but before getting into the meat of our class, let’s wrap up some ancillary items. First, a couple methods to run and cancel or work.
#region Methods
public void Run()
{
_worker.RunWorkerAsync();
}
public void Cancel()
{
if (!_isDisposed)
{
_worker.CancelAsync();
Dispose();
}
}
#endregion
For run, we will tell our worker to start, and for cancel, we will tell our worker to cancel. Also, we are going to implement IDisposable so there isn’t any bad stuff left hanging around when we are done with our background work. If you are not used to using this, you should get used to it.
Whew! That’s it for now. In part 3 of this series, we’re going to look at the guts of the class and finish up this object. Talk soon.
0 comments:
Post a Comment