(Post 02/11/2007) BackgroundWorker is a helper
class in the System.ComponentModel namespace for managing a worker thread.
BackgroundWorker uses the thread-pool, which recycles threads to avoid
recreating them for each new task. This means one should never call Abort
on a BackgroundWorker thread.
BackgroundWorker is a helper class in
the System.ComponentModel namespace for managing a worker thread. It provides
the following features:
- A "cancel" flag for signaling a worker to end without
using Abort
- A standard protocol for reporting progress, completion and cancellation
- An implementation of IComponent allowing it be sited in the Visual
Studio Designer
- Exception handling on the worker thread
- The ability to update Windows Forms controls in response to worker
progress or completion.
The last two features are particularly useful – it means
you don't have to include a try/catch block in your worker method, and
can update Windows Forms controls without needing to call Control.Invoke.
BackgroundWorker uses the thread-pool, which recycles
threads to avoid recreating them for each new task. This means one should
never call Abort on a BackgroundWorker thread.
Here are the minimum steps in using BackgroundWorker:
- Instantiate BackgroundWorker, and handle the DoWork event
- Call RunWorkerAsync, optionally with an object argument.
This then sets it in motion. Any argument passed to RunWorkerAsync
will be forwarded to DoWork's event handler, via the event argument's
Argument property. Here's an example:
class Program {
static BackgroundWorker bw = new BackgroundWorker();
static void Main() {
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync ("Message to worker");
Console.ReadLine();
}
static void bw_DoWork (object sender, DoWorkEventArgs e) {
// This is called on the worker thread
Console.WriteLine (e.Argument); // writes "Message to worker"
// Perform time-consuming task...
}
BackgroundWorker also provides a RunWorkerCompleted event
which fires after the DoWork event handler has done its job. Handling
RunWorkerCompleted is not mandatory, but one usually does so in order
to query any exception that was thrown in DoWork. Furthermore, code within
a RunWorkerCompleted event handler is able to update Windows Forms controls
without explicit marshalling; code within the DoWork event handler cannot.
To add support for progress reporting:
- Set the WorkerReportsProgress property to true
- Periodically call ReportProgress from within the DoWork event handler
with a "percentage complete" value, and optionally, a user-state
object
- Handle the ProgressChanged event, quering its event argument's ProgressPercentage
property
Code in the ProgressChanged event handler is free to
interact with UI controls just as with RunWorkerCompleted. This is typically
where you will update a progress bar.
To add support for cancellation:
- Set the WorkerSupportsCancellation property to true
- Periodically check the CancellationPending property from within
the DoWork event handler – if true, set the event argument's Cancel
property true, and return. (The worker can set Cancel true and exit
without prompting via CancellationPending – if it decides the job's
too difficult and it can't go on).
- Call CancelAsync to request cancellation.
Here's an example that implements all the above features:
using System;
using System.Threading;
using System.ComponentModel;
class Program {
static BackgroundWorker bw;
static void Main() {
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync ("Hello to worker");
Console.WriteLine ("Press Enter in the next 5 seconds to cancel");
Console.ReadLine();
if (bw.IsBusy) bw.CancelAsync();
Console.ReadLine();
}
static void bw_DoWork (object sender, DoWorkEventArgs e) {
for (int i = 0; i <= 100; i += 20) {
if (bw.CancellationPending) {
e.Cancel = true;
return;
}
bw.ReportProgress (i);
Thread.Sleep (1000);
}
e.Result = 123; // This gets passed to RunWorkerCopmleted
}
static void bw_RunWorkerCompleted (object sender,
RunWorkerCompletedEventArgs e) {
if (e.Cancelled)
Console.WriteLine ("You cancelled!");
else if (e.Error != null)
Console.WriteLine ("Worker exception: " + e.Error.ToString());
else
Console.WriteLine ("Complete - " + e.Result); // from DoWork
}
static void bw_ProgressChanged (object sender,
ProgressChangedEventArgs e) {
Console.WriteLine ("Reached " + e.ProgressPercentage + "%");
}
}
Press Enter in the next 5 seconds to cancel
Reached 0%
Reached 20%
Reached 40%
Reached 60%
Reached 80%
Reached 100%
Complete – 123
Press Enter in the next 5 seconds to cancel
Reached 0%
Reached 20%
Reached 40%
You cancelled |
Subclassing BackgroundWorker
BackgroundWorker is not sealed and provides a virtual
OnDoWork method, suggesting another pattern for its use. When writing
a potentially long-running method, one could instead – or as well – write
a version returning a subclassed BackgroundWorker, pre-configured to perform
the job asynchronously. The consumer then only need handle the RunWorkerCompleted
and ProgressChanged events. For instance, suppose we wrote a time-consuming
method called GetFinancialTotals:
public class Client {
Dictionary <string,int> GetFinancialTotals (int foo, int bar) {
... }
...
}
We could refactor it as follows:
public class Client {
public FinancialWorker GetFinancialTotalsBackground (int foo, int bar)
{
return new FinancialWorker (foo, bar);
}
}
public class FinancialWorker : BackgroundWorker {
public Dictionary <string,int> Result; // We can add typed fields.
public volatile int Foo, Bar; // We could even expose them
// via properties with locks!
public FinancialWorker() {
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
}
public FinancialWorker (int foo, int bar) : this() {
this.Foo = foo; this.Bar = bar;
}
protected override void OnDoWork (DoWorkEventArgs e) {
ReportProgress (0, "Working hard on this report...");
Initialize financial report data
while (!finished report ) {
if (CancellationPending) {
e.Cancel = true;
return;
}
Perform another calculation step
ReportProgress (percentCompleteCalc, "Getting there...");
}
ReportProgress (100, "Done!");
e.Result = Result = completed report data;
}
}
Whoever calls GetFinancialTotalsBackground then gets
a FinancialWorker – a wrapper to manage the background operation with
real-world usability. It can report progress, be cancelled, and is compatible
with Windows Forms without Control.Invoke. It's also exception-handled,
and uses a standard protocol (in common with that of anyone else using
BackgroundWorker!)
This usage of BackgroundWorker effectively deprecates
the old "event-based asynchronous pattern".
(Sưu tầm)
|