Thread in C# - Part 10: BackgroundWorker  
 

(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)


 
 

 
     
 
Công nghệ khác:


Thread in C# - Part 9: Apartments and Windows FormsThread in C# - Part 8: Synchronization Contexts
Thread in C# - Part 7: Wait HandlesThread in C# - Part 6: Thread State
Thread in C# - Part 5: Interrupt and AbortThread in C# - Part 4: Locking and Thread Safety
  Xem tiếp    
 
Lịch khai giảng của hệ thống
 
Ngày
Giờ
T.Tâm
TP Hồ Chí Minh
Hà Nội
 
   
New ADSE - Nhấn vào để xem chi tiết
Mừng Sinh Nhật Lần Thứ 20 FPT-APTECH
Nhấn vào để xem chi tiết
Bảng Vàng Thành Tích Sinh Viên FPT APTECH - Nhấn vào để xem chi tiết
Cập nhật công nghệ miễn phí cho tất cả cựu sinh viên APTECH toàn quốc
Tiết Thực Vì Cộng Đồng
Hội Thảo CNTT
Những khoảnh khắc không phai của Thầy Trò FPT-APTECH Ngày 20-11