.NET BackgroundWorker Summary
October 10th, 2007 by EricThe System.ComponentModel.BackgroundWorker class makes it easy to code a long running operation with the ability to provide progress, cancellation, and notification of completion. It is particularly nice for UI because while the long running operation executes on a thread pool thread, notifications of progress and completion are marshalled back to the “main” thread. (When I say “main” thread, I mean the one on which BackgroundWorker.RunWorkerAsync() is called.)
These are the steps for using BackgroundWorker:
Setup
- Create an instance of
System.ComponentModel.BackgroundWorker. This can be done manually, or by adding one as a component with the form designer if you’re doing UI. Usually you want to store this in a member variable so that the instance is available from theDoWorkevent handler. - Add a handler for the
BackgroundWorker.DoWorkevent. This handler should implement the long running operation, and will be invoked from another thread. - If you want progress, add a handler for the
BackgroundWorker.ProgressChangedevent and set theBackgroundWorker.WorkerReportsProgressproperty to true. This event handler will be called on the ”main” thread. If you forget to enable progress by setting the property, you’ll get anInvalidOperationExceptionif you try to provide progress. - If you want to allow cancellation, set the
BackgroundWorker.WorkerSupportsCancellationproperty to true. Likewise, you’ll get anInvalidOperaitonExceptionif you skip this step and try to cancel. - To get notification of completion, add a handler for the
BackgroundWorker.RunWorkerCompletedevent. This event handler will be called on the “main” thread when the long running operation stops, either because it is finished or it was cancelled.
Start
- Start the long running operation by calling
BackgroundWorker.RunWorkerAsync. You can optionally pass in an object that the long running operation can retrieve from theDoWorkEventArgs.Argumentproperty.
On the Main Thread
- Call
BackgroundWorker.CancelAsync()if the long running operation ought to be cancelled. Since the main thread isn’t blocked, this could be in response to a Cancel button being pressed, for example. - Handle
ProgressChangedevents, if needed. TheProgressChangedEventArgs.ProgressPercentagecould be used to update a progress bar. Additional progress state can be passed from the long running operation through theProgressChangedEventArgs.UserStateproperty. - Handle the
RunWorkerCompletedevent. There are several properties on theRunWorkerCompletedEventArgsthat can be populated from the long running operation. TheErrorproperty has anExceptionobject if one was thrown in the long running operation. TheCancelledproperty tells whether the operation was cancelled (but see the steps on the worker thread below). TheResultproperty can be used to retrieve a final outcome of the long running operation.
On the Worker Thread
- Do whatever work is required. If the operation throws an exception, the main thread can retrieve the exception from the
RunWorkerCompletedEventArgs.Errorproperty. - Check the
BackgroundWorker.CancellationPendingproperty. If the property is true, cancel the long running operation. Also, when the operation is cancelled, you should setDoWorkEventArgs.Cancelto true so that theRunWorkerCompletedEventArgs.Cancelledwill also be true. - Call
BackgroundWorker.ReportProgressas appropriate. You can pass a percent complete and an object with additional progress state if desired. - Set the
DoWorkEventArgs.Resultproperty when finished. The main thread can retrieve the result, if there is anything interesting to pass back to the main thread.
This post was inspired by Chapter 16 of Essential C# 2.0 by Mark Michaelis.
One subtle thing to be aware of. When you’re handling RunWorkerCompleted on the main thread, if you access e.Result, it will throw e.Error. That can be a problem if you tucked away a parameter you want to use when reporting an error and didn’t realize that behavior. For example:
private void GetWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
var info = (WorkerInfo)(e.Result).Info; // WARNING: This will throw e.Error
… // Attempt to use info in error message
}
else
{
… // No exception in DoWork handler – do normal thing.
}
}
But, you can also use it to your advantage, such as if the “do normal thing” can also throw an exception. For example:
private void GetWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
var info = (WorkerInfo)(e.Result).Info; // NOTE: This will throw e.Error if e.Error != null
… // Do normal thing. Exceptions will be handled in try/catch.
}
catch(Exception ex)
{
… // Report error that originated from DoWork handler or inside this handler
}
}
-Daniel