Multithreading is one of the most powerful concepts in programming. Using multithreading, you can break a complex task into multiple threads that execute independently of one another. One particularly good application of multithreading is in tasks that are synchronous in nature, such as web services calls. By default, web services calls are blocking calls--that is, the caller code will not continue until the web service returns a result. Because web services calls are often slow, this could result in sluggish client-side performance unless you take special steps to call the web service asynchronously. Another good use of multithreading is in tasks that takes a long time to process, such as calculating a complex mathematical formula.
By default, your Windows application uses a single thread of execution. You can use multithreading to create additional threads of execution so that each thread can be executed independently. One particular point you need to bear in mind is that Windows controls are not thread-safe. Put simply, it means that you cannot update the properties of a Windows control in a separate thread; only the main thread can update the controls. In this article, I will show you how multithreading has been simplified in Visual Studio 2005 using the BackgroundWorker component.
In the .NET 2.0 framework, Microsoft has made programming threads simpler by introducing the BackgroundWorker component. To demonstrate how the BackgroundWorker component helps to make your application more responsive, I will build a Windows application in this article that sums up a range of numbers. This example is purposely designed to be simple so that you can adapt it for your own use.
First, start Visual Studio 2005 Beta 2 and create a new Windows application. Populate the default Windows form with the controls shown in Figure 1:

Figure 1. Populating the default Windows form
Drag and drop the BackgroundWorker component (from the Components tab in Toolbox). The BackgroundWorker is a component; hence, it appears below the form in the component section (see Figure 2). The BackgroundWorker is a component that executes an operation on a separate thread.

Figure 2. Adding the BackgroundWorker component
Right-click on the BackgroundWorker component and select Properties. 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 (see Figure 3).

Figure 3. Setting the properties of the BackgroundWorker
Here is how it works. Basically, the user enters a number in the TextBox control
(txtNum) and clicks the Start button. The application will then
sum up all of the numbers from 0 to that number. The progress bar at the bottom
of the page will display the progress of the summation. The speed in which the
progress bar updates is dependent upon the number entered. For small numbers,
the progress bar will fill up very quickly. To really see the effect of how
summation works in a background thread, you should try a large number and watch
the progress bar update. You will notice that the window is still responsive
while the summation is underway. To abort the process, you can click on the
Cancel button. Once the summation is done, the result will be printed
on the Label control (lblResult).
|
Related Reading Programming .NET Components |
|
Let's switch to the code behind of the Windows form and do the coding. First, import the following namespace:
Imports System.ComponentModel
When the Start button is clicked, you first initialize some of the controls
on the form. You also change the cursor to an hourglass (Cursors.WaitCursor)
so that the user knows the application is working. You then get the BackgroundWorker
component to spin off a separate thread using the RunWorkAsync()
method. You pass the number entered by the user as the parameter for this method:
Private Sub btnStart_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnStart.Click
lblResult.Text = ""
btnCancel.Enabled = True
btnStart.Enabled = False
ProgressBar1.Value = 0
Me.Cursor = Cursors.WaitCursor
BackgroundWorker1.RunWorkerAsync(txtNum.Text)
End Sub
The DoWork event of the BackgroundWorker component will invoke
the SumNumbers() function (which I will define next) in a separate
thread. This event (DoWork) is fired when you call the RunWorkerAsync()
method (as was done in the previous step).
Private Sub BackgroundWorker1_DoWork( _
ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
'This method will run on a thread other than the UI thread.
'Be sure not to manipulate any Windows Forms controls created
'on the UI thread from this method.
Dim worker As System.ComponentModel.BackgroundWorker = _
CType(sender, System.ComponentModel.BackgroundWorker)
e.Result = SumNumbers(CType(e.Argument, Double), worker, e)
End Sub
The SumNumbers() function basically sums up all the numbers from
0 to the number specified. It takes in three arguments--the number to sum up
to, the BackgroundWorker, and the DoWorkEventArgs. Note that within
the For loop, you check to see if the user has clicked on the Cancel
button (the event will be defined later in this article) by checking the value
of the CancellationPending property. If the user has cancelled
the process, set e.Cancel to True. For every ten iterations,
I will also calculate the progress completed so far. If there is progress (when
the current progress percentage is greater than the last one recorded), then
I will update the progress bar by calling the ReportProgress()
method of the BackgroundWorker component. You should not call the ReportProgress()
method unnecessarily, as frequent calls to update the progress bar will freeze
the UI of your application.
It is important that note that in this method (which was invoked by the DoWork
event), you cannot directly access the Windows controls, as they are not thread-safe. Trying to do so will also trigger a runtime error, a useful feature new
in Visual Studio 2005.
Function SumNumbers( _
ByVal number As Double, _
ByVal worker As System.ComponentModel.BackgroundWorker, _
ByVal e As DoWorkEventArgs) As Double
Dim lastPercent As Integer = 0
Dim sum As Double = 0
For i As Double = 0 To number
'---check if user cancelled the process
If worker.CancellationPending = True Then
e.Cancel = True
Else
sum += i
If i Mod 10 = 0 Then
Dim percentDone As Integer = i / number * 100
'---update the progress bar if there is a change
If percentDone > lastPercent Then
worker.ReportProgress(percentDone)
lastPercent = percentDone
End If
End If
End If
Next
Return sum
End Function
The ProgressChanged event is invoked whenever the ReportProgress()
method is called. In this case, I used it to update my progress bar:
Private Sub backgroundWorker1_ProgressChanged( _
ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles BackgroundWorker1.ProgressChanged
'---updates the progress bar
ProgressBar1.Value = e.ProgressPercentage
End Sub
The RunWorkerCompleted event is fired when the thread (SumNumbers(),
in this case) has completed running. Here you will print the result accordingly
and change the cursor back to the default:
Private Sub BackgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles BackgroundWorker1.RunWorkerCompleted
If Not (e.Error Is Nothing) Then
MsgBox(e.Error.Message)
ElseIf e.Cancelled Then
MsgBox("Cancelled")
Else
lblResult.Text = "Sum of 1 to " & _
txtNum.Text & " is " & e.Result
End If
btnStart.Enabled = True
btnCancel.Enabled = False
Me.Cursor = Cursors.Default
End Sub
Finally, when the user clicks the Cancel button, you cancel the
process by calling the CancelAsync() method:
Private Sub btnCancel_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnCancel.Click
' Cancel the asynchronous operation.
BackgroundWorker1.CancelAsync()
btnCancel.Enabled = False
End Sub
To test the application, press F5 and enter a large number (say, 9999999)
and click the Start button. You should see the progress bar updating and the
cursor changed to an hourglass. When the process is completed, the result will
be printed in the Label control (see Figure 4).

Figure 4. Testing the application
Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.
Return to the OnDotNet.com
Copyright © 2009 O'Reilly Media, Inc.