3.15. Handle Asynchronous Tasks Safely
One of .NET's most
impressive features is its extensive support for multithreaded
programming. However, as most programmers discover at some point in
their lives, multithreaded programming isn't
necessarily easy.
Note: Need to conduct a time-consuming task in the background
without dealing with threading issues? The new BackgroundWorker class
makes it easy.
One of the main challenges with Windows applications is that
it's not safe to modify a form or control from a
background thread, which means that after your background task is
finished, there's no straightforward way to update
your application's interface. You can use the
Control.Invoke( ) method to marshal a method to
the correct thread, but other problems then appear, such as
transferring the information you need to make the update.
Fortunately, all of these headaches can be avoided thanks to the new
BackgroundWorker component.
3.15.1. How do I do that?
The BackgroundWorker component gives you a
foolproof way to run a time-consuming task on a separate, dedicated
thread. This ensures that your application interface remains
responsive, and it allows your code to carry out other tasks in the
foreground. Best of all, the underlying complexities of multithreaded
programming are hidden. Once the background process is complete, you
simply handle an event, which fires on the main thread. In addition,
the BackgroundWorker supports progress reporting
and canceling.You can either create a BackgroundWorker object
programmatically, or you can drag it onto a form from the Components
tab of the toolbox. To start your background operation, you call the
RunWorkerAsync( )
method. If you need to pass an input value to this process, you can
supply it as an argument to this method (any type of object is
allowed):
Worker.RunWorkerAsync(inputValue)Next, you need to handle the DoWork event to
perform the background task. The DoWork event
fires on the background thread, which means at this point you
can't interact with any other part of your
application (unless you're willing to use locks or
other techniques to safeguard access). Typically, the
DoWork event handler retrieves the input value
from the DoWorkEventArgs.Argument property and
then carries out the time-consuming operation. Once the operation is
complete, you simply set the
DoWorkEventArgs.Result property with the result.
You can use any data type or even a custom object.
Here's the basic pattern you'll
use:
Private Sub backgroundWorker1_DoWork(ByVal sender As Object, _Finally, the BackgroundWorker fires a
ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
' Get the information that was supplied.
Dim Input As Integer = CType(e.Argument, Integer)
' (Perform some time consuming task.)
' Return the result.
e.Result = Answer
End Sub
RunWorkerCompleted
event to notify your application that the process is complete. At
this point, you can retrieve the result from
RunWorkerCompletedEventArgs and update the form
accordingly:
Private Sub backgroundWorker1_RunWorkerCompleted( _Example 3-6 shows a form that puts all of these
ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
result.Text = "Result is: " & e.Result.ToString( )
End Sub
parts together. It performs a time-limited loop for a number of
seconds that you specify. This example also demonstrates two more
advanced techniques: cancellation and progress. To cancel the
operation, you simply need to call the
BackgroundWorker.CancelAsync( ) method. Your
DoWork event-handling code can then check to see
if the main form is attempting to cancel the operation and exit
gracefully. To maintain progress information, your
DoWork event-handling code needs to call the
BackgroundWorker.ReportProgress( ) method and
provide an estimated percent complete (where 0% means
"just started" and 100% means
"completely finished"). The form
code can respond to the ProgressChanged event to
read the new progress percentage and update another control, such as
a ProgressBar. Figure 3-12 shows
this application in action.
Example 3-6. An asynchronous form with the BackgroundWorker
Public Class AsyncForm
Private Sub startAsyncButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles startAsyncButton.Click
' Disable the Start button until
' the asynchronous operation is done.
startAsyncButton.Enabled = False
' Enable the Cancel button while
' the asynchronous operation runs.
cancelAsyncButton.Enabled = True
' Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync(Int32.Parse(txtWaitTime.Text))
End Sub
' This event handler is where the actual work is done.
Private Sub backgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
' Get the information that was supplied.
Dim Worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim StartTime As DateTime = DateTime.Now
Dim SecondsToWait As Integer = CType(e.Argument, Integer)
Dim Answer As Single = 100
Do
' Check for any cancellation requests.
If Worker.CancellationPending Then
e.Cancel = True
Return
End If
' Continue calculating the answer.
Answer *= 1.01
' Report the current progress (percentage complete).
Worker.ReportProgress(( _
DateTime.Now.Subtract(StartTime).TotalSeconds / SecondsToWait) * 100)
Thread.Sleep(50)
Loop Until DateTime.Now > (StartTime.AddSeconds(SecondsToWait))
e.Result = Answer
End Sub
' This event handler fires when the background work
' is complete.
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
' Check what the result was, and update the form.
If Not (e.Error Is Nothing) Then
' An exception was thrown.
MessageBox.Show(e.Error.Message)
ElseIf e.Cancelled Then
' Check if the user cancelled the operation.
result.Text = "Cancelled"
Else
' The operation succeeded.
result.Text = "Result is: " & e.Result.ToString( )
End If
startAsyncButton.Enabled = True
cancelAsyncButton.Enabled = False
End Sub
' This event handler updates the progress bar.
Private Sub backgroundWorker1_ProgressChanged( _
ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles backgroundWorker1.ProgressChanged
Me.progressBar1.Value = e.ProgressPercentage
End Sub
Private Sub cancelAsyncButton_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles cancelAsyncButton.Click
' Cancel the asynchronous operation.
Me.backgroundWorker1.CancelAsync( )
cancelAsyncButton.Enabled = False
End Sub
End Class
Figure 3-12. Monitoring a background task

3.15.2. What about...
...other scenarios where you can use the
BackgroundWorker? This example used the
BackgroundWorker with a long-running background
calculation. Other situations in which the
BackgroundWorker proves to be just as
indispensable include:Contacting a web serviceDownloading a file over the InternetRetrieving data from a databaseReading or writing large amounts of data
3.15.3. Where can I learn more?
The MSDN reference includes a detailed walkthrough for using the
BackgroundWorker, and other topics that tackle
multithreaded programming in detail. Look up the
"background operations" index entry
to see a slightly different approach that uses the
BackgroundWorker to calculate Fibonacci
numbers.