Visual Basic 1002005 [A Developers Notebook] [Electronic resources]

شرکت رسانه او ریلی

نسخه متنی -صفحه : 97/ 58
نمايش فراداده

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, _
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

Finally, the BackgroundWorker fires a 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( _
ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
result.Text = "Result is: " & e.Result.ToString( )
End Sub

Example 3-6 shows a form that puts all of these 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 service

Downloading a file over the Internet

Retrieving data from a database

Reading 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.