(Print this page)

The BackgroundWorker component
Published date: Friday, December 1, 2006
On: Moer and Éric Moreau's web site

For those of you who had the chance to be at the first Montreal’s Code Camp in October, you might have seen me giving a session on this month topic. The BackgroundWorker component is a new component of the .Net framework 2.0 but gives nothing more to programmers that did not already exist in previous version. This new component provides an easy way to implement multithreading in Windows Forms application. This component helps programmers offering a responsive UI to their users. It also lets programmers’ reports progress and handles cancellation easily.

The purpose of the component

As I have already said, this component brings nothing new to programmers. It is a wrapper over the System.Threading that hides many of its tasks and requirements. Microsoft found that most programmers were not using the multithreading feature in many cases because it was too complex. This component is dedicated to Windows forms.

This new component helps to make your application more responsive without adding complexity.

Because the form and the process are run on different threads, the process cannot directly manipulate the controls on the form (like reporting progress) and the controls cannot directly interfere with the process (like cancelling it).

This component relies on events to pass messages between the threads.

Creating a demo application

Before starting to work with the component, we need to create a small application that we will use to test the BackgroundWorker component.

Figure 1: The demo application

This application will do some lengthy calculations (the sum of all numbers from 1 to the number entered into the TextBox control) so that you can see progress and have a chance to cancel the process. You will also be able to discover that the form is still responsive and has no problem repainting itself while the process runs (a very common problem of windows applications).

Placing a component on the form

In order to be the simplest it can be for Windows Forms application programmers, Microsoft decided to gives us a component that we can add to our forms. You will find this component into the Components tab of your toolbox. Because it is a component, it will appear in the component tray (a grey zone under the form).

Figure 2: Adding a component to the form

In the properties dialog of the component, set the WorkerReportsProgress and WorkerSupportsCancellation properties to True so that the component can report on the progress of the thread as well as be cancelled halfway through the thread. These 2 properties are set to False by default (probably to save some resources). We will implement these 2 features so the properties need to be turned on.

Starting the asynchronous process

When we are ready to start the asynchronous process, we need to call the RunWorkerAsync method of the BackgroundWorker component. This method will create a thread for us and call the DoWork event of the BackgroundWorker component.

This is the code you find into the start button:

'We cannot start more then one calculation at a single time
btnStart.Enabled = False
'We can now cancel the process
btnCancel.Enabled = True
'Reset some other controls 
ProgressBar1.Value = 0
lblResult.Text = "Processing..."
Me.Cursor = Cursors.WaitCursor

'Starts the asynchronous process
BackgroundWorker1.RunWorkerAsync(Convert.ToInt32(txtNumber.Text))

The DoWork event

This event is called by the RunWorkerAsync method. When you are in this event, remember that you are in fact in a thread different then the one that manipulates the form. That means that you should never manipulate a control of the form.

This is the code you will find in my DoWork event handler.

'Never manipulate any Windows Forms controls here
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
e.Result = SumNumbers(Convert.ToInt32(e.Argument), worker, e)

The main thing it does is to call the SumNumbers method that we will find later. This method needs a handler to the BackgroundWorker component so that it can check for cancellation and also report progress to it.

Here is the famous method you are waiting to see:

Private Function SumNumbers( _
       ByVal pNumber As Integer, _
       ByVal pWorker As BackgroundWorker, _
       ByVal e As DoWorkEventArgs) As Double

    Dim intLastPercent As Integer = 0
    Dim intProgress As Integer
    Dim dblSum As Double = 0

    For intI As Integer = 0 To pNumber
        'check for pending cancellation 
        If pWorker.CancellationPending = True Then
            e.Cancel = True
            Exit For
        Else
            dblSum += intI
            If intI Mod 10 = 0 Then
                intProgress = CInt(intI / pNumber * 100)
                'reports a progress change
                If intProgress > intLastPercent Then
                    pWorker.ReportProgress(intProgress)
                    intLastPercent = intProgress
                End If
            End If
        End If
    Next
    Return dblSum
End Function

In addition to the parameter you were expecting (the number to which you want to stop looping), you see 2 additional parameters that you must declare. The first one is the handle to the BackgroundWorker that is calling this method. The second parameter is a structure containing specific arguments to this component.

The main part of this method is a loop from 1 to the value you entered into the Textbox control (received here into the pNumber argument). In each iteration, the loop index (intI variable) is added to the sum (dblSum variable).

The other portions of this method will be explained a bit later.

Reporting progress

Reporting the progress is very easy. When you look inside the SumNumbers method, you see at some point that the ReportProgress method of the BackgroundWorker is called. This method will raise the ProgressChanged event.

The code you will find in this event is the following:

ProgressBar1.Value = e.ProgressPercentage

Don’t forget to not update the progress bar from the SumNumbers method. This method is not in the same thread as the form and its controls.

Handling cancellation

The BackgroundWorker component let us handles a cancellation feature. The cancellation is done in two phases.

The first part is normally behind a button (the Cancel button in my sample). We need to call a method (CancelAsync) of the BackgroundWorker component. This method will set the CancellationPending property to True. This is the code you find in my Cancel button Click event:

BackgroundWorker1.CancelAsync()

This line alone won’t stop the process. You have to handle it yourself in your process that is running asynchronously (somewhere on another thread). This is why you find these lines in the SumNumbers method:

If pWorker.CancellationPending = True Then
    e.Cancel = True
    Exit For

These lines are used to stop the calculation. One of the reasons why it is implemented this way is to let you close and dispose of any resource that your process could be using.

Process completion

When the process completes, the RunWorkerCompleted event is triggered. It doesn’t mean that your process has completed successfully. An error could have occurred or the user could have cancelled the process. This is why you have to check the Error and Cancelled property of the e argument. If both are still False, only then you are assured that the process has completed successfully.

This is the code found into the RunWorkerCompleted event:

'Check the state of completion
If Not (e.Error Is Nothing) Then
    'an error occured and stopped the calculation
    lblResult.Text = e.Error.Message
ElseIf e.Cancelled Then
    'the process was cancelled by the user
    lblResult.Text = "Process cancelled"
Else
    'the process completed normally
    lblResult.Text = "The sum is " & e.Result.ToString
End If
'Reset some other controls 
btnStart.Enabled = True
btnCancel.Enabled = False
Me.Cursor = Cursors.Default

Testing the application

Enter a number that is big enough to let you play with the interface a bit. I found that 100,000,000 on my PC let me enough time to see that I can drag the form, see the progress changed, try the cancel button, … Isn’t it the kind of application you like?

Conclusion

Microsoft did a great job of simplifying the multithreading for our Windows Forms application. I hope that the sample provided here will incite you using this component if you have not already started. I am sure your users will also thank you.

I hope you appreciated the topic and see you next month.


(Print this page)