Introduction
Whenever you have a long running process, it's usually a good idea to have it process in a background thread. This keeps your application responsive. If it's a piece of code that is only being called from a few spots, the BackgroundWorker is often the best solution. If it's a class library that will be called from lots of different applications, I prefer to encapsulate the threading within the class.
Background
The basic premise is to create an abstract base class. Every method that can be called asynchronously will have a corresponding Async method and Completed event. This allows the method to be called either asynchronously or not depending on the needs of the caller.
Using the Code
To use the code, you'll need to create the new abstract base class and threaded wrapper methods for all methods that need asynchronous execution.
public event EventHandler<myasynceventargs> MyMethodComplete;
protected void FireMyMethodComplete(int result, object state)
{
...
}
public abstract int MyMethod(string stringParameter, int intParameter);
public void MyMethodAsync(string stringParameter, int intParameter, object state) { ... }
You will need the abstract declaration and event declarations for each method to be called asynchronously. Any method that doesn't need to be threaded should not be declared in this base class.
public void MyMethodAsync(string stringParameter, int intParameter, object state)
{
MyMethodParameters parameters = new MyMethodParameters()
{
StringParameter = stringParameter,
IntParameter = intParameter,
...
CallerStateObject = state,
CallingDispatcher =
System.Windows.Threading.Dispatcher.CurrentDispatcher
};
System.Threading.ThreadPool.QueueUserWorkItem(MyMethodThreaded, parameters);
}
The key to this is the dispatcher. By storing a reference to the calling thread's dispatcher, we can later raise events on that thread.
private void MyMethodThreaded(object p)
{
MyMethodParameters parameters = (MyMethodParameters)p;
int result = MyMethod(parameters.StringParameter, parameters.IntParameters);
parameters.CallingDispatcher.BeginInvoke(
new Action<int,>(FireMyMethodCompleteEvent),
result,
parameters.CallerStateObject);
}
Within the threaded method, we call the actual method. We then use the dispatcher we saved earlier to call the event handler on the proper thread. As promised, this keeps the class with the business logic simple.
public class MyClass : MyAsyncBaseClass
{
public override int MyMethod(int stringParameter, int intParameter)
{
return 42;
}
}
Using the class is also simple.
...
MyClass _myClass = new MyClass();
_myClass.MyMethodComplete += MyClass_MyMethodComplete
...
_myClass.MyMethodAsync(AStringParameter, AnIntParameter,
APassthroughStateObject);
...
void MyClass_MyMethodComplete(object sender, MyAsyncEventArgs e)
{
}
Conclusion
You've no doubt noticed this results in far more code than simply using the BackgroundWorker. However, it requires less code in the projects which consume your class. For a class which is reused frequently, there will be less cumulative code.
History
- 17th October, 2009: Initial post
- 9th December, 2010: Article updated