Asynchronous Windows Forms Programming (Cont.)
.NET 2.0 BackgroundWorker
The Windows Forms team and the .NET architects are well-aware of the problems just described. To address them, the next version of .NET (Whidbey) contains a new component called BackgroundWorker defined in the System.ComponentModel namespace. If you have access to the Whidbey beta distributed at PDC, you can find BackgroundWorker in the Components tab of a Windows Forms project. If you drop it on a form, you can use BackgroundWorker to dispatch asynchronous work, report progress and completion, and do all that while encapsulating the interaction with ISynchronizeInvoke. Using this approach gives developers a much smoother and superior programming model. Listing 2 shows the definition of BackgroundWorker and its supporting classes.
Use BackgroundWorker to dispatch asynchronous work, report progress and completion, and do all that while encapsulating the interaction with ISynchronizeInvoke.
BackgroundWorker has a public delegate called DoWork of the type DoWorkEventHandler. To invoke a method asynchronously, wrap a method with a matching signature to DoWorkEventHandler and add it as a target to DoWork. Then, call the RunWorkerAsync() method to invoke the method on a thread from the thread pool:BackgroundWorker backgroundWorker;
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += OnDoWork;
void OnDoWork(object sender,DoWorkEventArgs doWorkArgs)
BackgroundWorker offers two overloaded versions of RunWorkerAsync():public void RunWorkerAsync();
public void RunWorkerAsync(object argument);
You can use the argument parameter to pass any argument to the asynchronous method. Internally, RunWorkerAsync() will construct a DoWorkEventArgs object containing the argument, invoke the DoWork delegate asynchronously, and pass it the DoWorkEventArgs object and itself as the sender.
The asynchronous method can access the Argument property of DoWorkEventArgs to retrieve the argument passed to RunWorkerAsync(). The asynchronous method should also set the value of the Result property of DoWorkEventArgs with the retuned value.
Any party interested in being notified when the asynchronous method is completed should subscribe to the RunWorkerCompleted member delegate of BackgroundWorker. RunWorkerCompleted is a delegate of the type RunWorkerCompletedEventHandler. The completion notification method accepts a parameter of type RunWorkerCompletedEventArgs, which contains the result of the method execution (the value you set in the Result property of DoWorkEventArgs inside the asynchronous method), as well as error and cancellation information.
When the asynchronous method execution is completed, BackgroundWorker cannot simply invoke the RunWorkerCompleted delegate because that invocation will be on the thread from the thread pool, and any control or form that subscribed to RunWorkerCompleted cannot be called directly. Instead, BackgroundWorker checks whether each of the target objects in RunWorkerCompleted supports ISynchronizeInvoke, and if invoke is required. If so, it will marshal the call to the owning thread of the target object.
To support progress reports, BackgroundWorker provides a member delegate called ProgressChanged of the type ProgressChangedEventHandler. Any party interested in progress notification should subscribe to ProgressChanged. When the asynchronous method wishes to notify about progress, it calls BackgroundWorker's method, ReportProgress, to correctly marshal the progress notification to any Windows Forms object.
To cancel a method, anybody from any thread can call BackgroundWorker's CancelAsync(). Calling CancelAsync() results in having the CancellationPending property of BackgroundWorker return true. Inside the asynchronous method, you should periodically check the value of CancellationPending, and if it is true, you should set the Cancel property of DoWorkEventArgs to true and return from the method. In the completion method you could check the value of the Cancelled property of RunWorkerCompletedEventArgs to detect whether the method has run to its completion or it was cancelled.
In case you derive from BackgroundWorker in order to specialize its behavior, the subclass you provide may also want to be notified of progress reports and completion events. One way to do that would be to provide event handling methods at the scope of the subclass, and add these methods as targets for the ProgressChanged or RunWorkerCompleted member delegates. However, it is a bit inconvenient to have to subscribe to events of your own base class. To that end, BackgroundWorker provides two protected virtual methods: OnProgressChanged() and OnRunWorkerCompleted(). These methods invoke their respective delegates, ProgressChanged and RunWorkerCompleted. You can override them, and perform some pre or post event processing:public class MyBackgroundWorker : BackgroundWorker
protected override void OnRunWorkerCompleted( RunWorkerCompletedEventArgs completedArgs)
// do some pre-event processing, then:
Note that it is very important to call BackgroundWorker's implementation of these methods, otherwise the events will never be raised.
BackgroundWorker has two Boolean properties that control progress reports and cancellation?WorkerReportsProgress and WorkerSupportsCancellation. Both properties are false by default, but of course you need to set them to true in most cases.
Listing 3 shows the same application as in Listing 1, except this time it uses a single BackgroundWorker component. Note how smooth and easy the code is for handling progress reports and completion. Because you are guaranteed to always execute on the correct thread, you can access and assigned the controls' properties directly.