Background Worker


If you haven’t written any Windows applications until now, you can skip this section of the chapter and keep it for later. Just remember using threads from Windows applications adds another complexity, and you should come back here after reading the Windows Forms chapters (Chapters 28 to 30) or WPF chapter (Chapter 31). In any case, the Windows Forms application demonstrated here is very simple from a Windows Forms viewpoint.

Windows Forms and WPF controls are bound to a single thread. With every control, you can only invoke methods from the thread that created the control. This also means that if you have a background thread, you cannot directly access the UI controls from this thread.

The only methods with Windows Forms controls that you can invoke from a different thread than the creator thread are Invoke(), BeginInvoke(), EndInvoke(), and the property InvokeRequired. BeginInvoke() and EndInvoke() are asynchronous variants of Invoke(). These methods switch to the creator thread to invoke the method that is assigned to a delegate parameter that you can pass to these methods. Using these methods it not that easy, which is why since .NET 2.0 a new component together with a new asynchronous pattern was invented: the BackgroundWorker.

The class BackgroundWorker defines methods, properties, and events, as described in the following table.

Open table as spreadsheet

BackgroundWorker Members

Description

IsBusy

The property IsBusy returns true while an asynchronous task is active.

CancellationPending

The property CancellationPending returns true after the CancelAsync() method was invoked. If this property is set to true, the asynchronous task should stop its work.

RunWorkerAsync() DoWork

The method RunWorkerAsync() fires the DoWork event to start the asynchronous task in a separate thread.

CancelAsync() WorkerSupportsCancellation

If cancellation is enabled (by setting the WorkerSupportsCancellation property to true), the asynchronous task can be canceled with the CancelAsync() method.

ReportProgress() ProgressChanged WorkerReportsProgress

If the WorkerReportsProgress property is set to true, the BackgroundWorker can give interim feedback about the progress of the asynchronous task. The asynchronous task provides feedback about the percentage of work completed, by invoking the method ReportProgress(). This method than fires the ProgressChanged event.

RunWorkerCompleted

The RunWorkerCompleted event is fired as soon as the asynchronous task is completed, no matter if it was canceled or not.

The sample application demonstrates the use of the BackgroundWorker control in a Windows Forms application by doing a task that takes some time. Create a new Windows Forms application and add three Label controls, three TextBox controls, two Button controls, one ProgressBar, and one BackgroundWorker to the form, as shown in Figure 18-3.

image from book
Figure 18-3

Configure the properties of the controls as listed in the following table.

Open table as spreadsheet

Control

Property and Events

Value

Label

Text

X:

TextBox

Name

textbox

Label

Text

Y:

TextBox

Name

textBoxY

Label

Text

Result:

TextBox

Name

textBoxResult

Button

Name

buttonCalculate

 

Text

Calculate

 

Click

OnCalculate

Button

Name

buttonCancel

 

Text

Cancel

 

Enabled

False

 

Click

OnCancel

ProgressBar

Name

progressBar

BackgroundWorker

Name

backgroundWorker

 

DoWork

OnDoWork

 

RunWorkerCompleted

OnWorkCompleted

Add the struct CalcInput to the project. This struct will be used to contain the input data from the TextBox controls.

  public struct CalcInput {    public CalcInput(int x, int y)    {       this.x = x;       this.y = y;    }    public int x;    public int y; } 

The method OnCalculate() is the event handler for the Click event from the Button control named buttonCalculate. In the implementation buttonCalculate is disabled, so the user cannot click the button once more until the calculation is completed. To start the BackgroundWorker invoke the method RunWorkerAsync(). The BackgroundWorker uses a thread pool thread to do the calculation. RunWorkerAsync() requires the input parameters that are passed to the handler that is assigned to the DoWork event.

 private void OnCalculate(object sender, EventArgs e) {    this.buttonCalculate.Enabled = false;    this.textBoxResult.Text = String.Empty;    this.buttonCancel.Enabled = true;    this.progressBar.Value = 0;     backgroundWorker.RunWorkerAsync(new CalcInput(       int.Parse(this.textBoxX.Text), int.Parse(this.textBoxY.Text))); }

The method OnDoWork() is connected to the DoWork event of the BackgroundWorker control. With the DoWorkEventArgs the input parameters are received with the property Argument. The implementation simulates functionality that takes some time with a sleep time of 5 seconds. After sleeping, the result of the calculation is written to the Result property of DoEventArgs. If you add the calculation and sleep to the OnCalculate() method instead, the Windows application is blocked from user input while this is active. However, here a separate thread is used and the user interface is still active.

 private void OnDoWork(object sender, DoWorkEventArgs e) {    CalcInput input = (CalcInput)e.Argument;    Thread.Sleep(5000);    e.Result = input.x + input.y; } 

After OnDoWork is completed, the background worker fires the RunWorkerCompleted event. The method OnWorkCompeted() is associated with this event. Here, the result is received from the Result property of the RunWorkerCompletedEventArgs parameter, and this result is written to the result TextBox control. When firing the event, the BackgroundWorker control changes control to the creator thread, so there’s no need to use the Invoke methods of the Windows Forms controls, and you can invoke properties and methods of Windows Forms controls directly.

 private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e) {    this.textBoxResult.Text = e.Result.ToString();     this.buttonCalculate.Enabled = true;    this.buttonCancel.Enabled = false;    this.progressBar.Value = 100; }

Now you can test the application and will see that the calculation runs independently of the UI thread, the UI is still active, and the Form can be moved around. However, the cancel and progress bar functionality still needs implementation.

Enable Cancel

To enable the cancel functionality to stop the thread’s progress while it is running, you must set the BackgroundWorker property WorkerSupportsCancellation to True. Next, you have to implement the OnCancel handler that is connected to the Click event of the control buttonCancel. The BackGroundWorker control has the CancelAsync() method to cancel an asynchronous task that is going on.

 private void OnCancel(object sender, EventArgs e) {    backgroundWorker.CancelAsync(); }

The asynchronous task is not canceled automatically. In the OnDoWork() handler that does the asynchronous task, you must change the implementation to examine the CancellationPending property of the BackgroundWorker control. This property is set as soon as CancelAsync() is invoked. If a cancellation is pending, set the Cancel property of DoWorkEventArgs to true and exit the handler.

 private void OnDoWork(object sender, DoWorkEventArgs e) {    CalcInput input = (CalcInput)e.Argument;        for (int i = 0; i < 10; i++)    {       Thread.Sleep(500);        if (backgroundWorker.CancellationPending)       {          e.Cancel = true;          return;       }    }    e.Result = input.x + input.y; }

The completion handler OnWorkComleted() is invoked if the asynchronous method has completed successfully or if it was canceled. If it was canceled you cannot access the Result property, as this throws an InvalidOperationException with the information that the operation has been canceled. So, you have to check the Cancelled property of RunWorkerCompletedEventArgs and behave accordingly.

 private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e) {    if (e.Cancelled)    {       this.textBoxResult.Text = "Cancelled";    }    else    {       this.textBoxResult.Text = e.Result.ToString();    }    this.buttonCalculate.Enabled = true;    this.buttonCancel.Enabled = false; }

Running the application once more, you can cancel the asynchronous progress from the user interface.

Enable Progress

For getting progress information to the user interface, you must set the BackgroundWorker property WorkerReportsProgress to True.

With the OnDoWork method, you can report the progress to the BackgroundWorker control with the ReportProgress() method.

 private void OnDoWork(object sender, DoWorkEventArgs e) {    CalcInput input = (CalcInput)e.Argument;    for (int i = 0; i < 10; i++)    {       Thread.Sleep(500);       backgroundWorker.ReportProgress(i * 10);       if (backgroundWorker.CancellationPending)       {          e.Cancel = true;          return;       }    }    e.Result = input.x + input.y; }

The method ReportProgress() fires the ProgressChanged event of the BackgroundWorker control. This event changes the control to the UI thread.

Add the method OnProgressChanged() to the ProgressChanged event, and in the implementation set a new value to the progress bar control that is received from the property ProgressPercentage of ProgressChangedEventArgs.

 private void OnProgressChanged(object sender, ProgressChangedEventArgs e) {    this.progressBar.Value = e.ProgressPercentage; }

In the OnWorkCompleted() event handler, the progress bar finally is set to the 100% value.

 private void OnWorkCompleted(object sender, RunWorkerCompletedEventArgs e) {    if (e.Cancelled)    {       this.textBoxResult.Text = "Cancelled";    }    else    {       this.textBoxResult.Text = e.Result.ToString();    }    this.buttonCalculate.Enabled = true;    this.buttonCancel.Enabled = false;    this.progressBar.Value = 100; }

Figure 18-4 shows the running application while the calculation is just active.

image from book
Figure 18-4




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net