Recipe 14.21. Running Procedures in Threads


Problem

You would like to perform some involved background data processing but keep the user interface for your application responsive to user interaction.

Solution

Sample code folder: Chapter 14\UsingThreads

Use a BackgroundWorker control (or class) to manage the interaction between the main process and a worker thread.

Discussion

This recipe's sample code starts a background worker thread that does some work, reporting its progress back to the main thread on a regular basis. The main thread has the option to cancel the worker thread. Create a new Windows Forms application, and add the following controls to Form1:

  • A Button control named StartWork. Change its Text property to Start.

  • A Button control named StopWork. Change its Text property to Stop, and set its Enabled property to False.

  • A Label control named WorkStatus. Change its Text property to Not started.

  • A ProgressBar control named WorkProgress.

  • A BackgroundWorker control named BackgroundActivity. Change both the WorkerReportsProgress and WorkerSupportsCancellation properties to true.

Arrange the controls nicely so they look like Figure 14-16.

Figure 14-16. Controls for the background activity sample


Add the following Imports statement at the top of the source-code file for Form1:

 Imports System.ComponentModel 

Now add the following source code to the Form1 class:

 Private Sub BackgroundActivity_DoWork( _       ByVal sender As Object, ByVal e As _       System.ComponentModel.DoWorkEventArgs) _       Handles BackgroundActivity.DoWork    ' ----- The background work starts here.    Dim theBackground As BackgroundWorker    ' ----- Call the background thread.    theBackground = CType(sender, BackgroundWorker)    TheBusyWork(theBackground)    ' ----- Check for a cancellation.    If (theBackground.CancellationPending = True) Then _       e.Cancel = True End Sub Private Sub BackgroundActivity_ProgressChanged( _       ByVal sender As Object, ByVal e As _       System.ComponentModel.ProgressChangedEventArgs) _       Handles BackgroundActivity.ProgressChanged    ' ----- The background task updated its progress.    WorkProgress.Value = e.ProgressPercentage End Sub Private Sub BackgroundActivity_RunWorkerCompleted( _       ByVal sender As Object, ByVal e As _       System.ComponentModel.RunWorkerCompletedEventArgs) _       Handles BackgroundActivity.RunWorkerCompleted    ' ----- Finished.    If (e.Cancelled = True) Then       WorkStatus.Text = "Cancelled."    Else       WorkStatus.Text = "Complete."    End If    WorkProgress.Visible = False    WorkProgress.Value = 0    StopWork.Enabled = False    StartWork.Enabled = True End Sub Private Sub TheBusyWork(ByVal workerLink As BackgroundWorker)    ' ----- Perform some work.    For counter As Integer = 1 To 10       ' ----- See if we should jump out now.       If (workerLink.CancellationPending = True) Then _          Exit For       ' ----- Take a nap for 2 seconds.       Threading.Thread.Sleep(2000)       ' ----- Inform the primary thread that we've       '       made significant progress.       workerLink.ReportProgress(counter * 10)    Next counter End Sub Private Sub StartWork_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles StartWork.Click    ' ----- Start the background process.    StartWork.Enabled = False    StopWork.Enabled = True    WorkStatus.Text = "Progress…"    WorkProgress.Value = 0    WorkProgress.Visible = True    BackgroundActivity.  RunWorkerAsync( ) End Sub Private Sub StopWork_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles StopWork.Click    ' ----- Tell the worker thread to stop.    BackgroundActivity.CancelAsync( ) End Sub 

Run the program, and click on the Start button. The progress will update as the background worker proceeds through its activity loop. You can interrupt the back-ground worker by clicking on the Stop button, although it won't actually stop until the end of the current two-second sleep.

Processes running in Windows have the option of dividing their work among separate threads of execution within those processes. By default, Visual Basic processes include only a single thread: the process itself. However, you can start one or more background worker threads to perform some activity apart from the flow of the primary application.

The .NET Framework includes threading support through the System.Threading namespace, and specifically through the Thread class in that namespace. While using the THRead class is relatively simple, you have to develop or enhance the class if you want standardized interactions to occur between your primary and worker threads.

The BackgroundWorker control, part of the System.ComponentModel namespace, implements a lot of these interaction features for you. To use the control, simply add it to your form. You can also use it as a class by declaring it using the WithEvents keyword:

 Private WithEvents BackgroundActivity _    As System.ComponentModel.BackgroundWorker 

When you are ready to initiate the background work, call the BackgroundWorker's RunWorkerAsync() method. This triggers the DoWork event. In this event handler, call the method that will perform the background work. The sample code passes the BackgroundWorker instance to the worker method. You don't have to pass this information, but it makes it easier to communicate back to the primary thread if you do.

For example, if you want the worker thread to report its progress, set the control's WorkerReportsProgress property to true, then monitor the control's ProgressChanged event. Calls to the control's ReportProgress() method by the work trigger this event in the primary thread.

This communication works both ways. Setting the control's WorkerSupportsCancellation property to TRue allows the primary thread to request a cancellation of the work by calling the CancelAsync() method. This sets the control's CancellationPending property, as viewed by the worker thread.

Threads make background processing easy, but interactions between threads can be problematic. The issue is that if two threads wish to update the same object instance, there is no guarantee that they will update them in a specific order. Consider a class with three members. Updating these three members occurs over multiple statements:

 Private SomeInstance As SomeClass Private Sub UpdateInstance(ByVal scalar As Integer)    SomeInstance.Member1 = 10 * scalar    SomeInstance.Member2 = 20 * scalar    SomeInstance.Member3 = 30 * scalar End Sub 

But what happens when two different threads call the UpdateInstance() method at the same time (assuming that they are sharing the SomeInstance variable)? Because of the way that threading works, it's possible that the calls could get interleaved in ways that corrupt the data. Suppose thread #1 calls UpdateInstance(2) and thread #2 calls UpdateInstance(3). It's possible the statements within UpdateInstance() could be called in this order:

 SomeInstance.Member1 = 10 * 2 ' From Thread #1 SomeInstance.Member1 = 10 * 3 ' From Thread #2 SomeInstance.Member2 = 20 * 3 ' From Thread #2 SomeInstance.Member2 = 20 * 2 ' From Thread #1 SomeInstance.Member3 = 30 * 2 ' From Thread #1 SomeInstance.Member3 = 30 * 3 ' From Thread #2 

After this code, Member1 and Member3 is set based on the call from thread #2, but Member2 retains the value from thread #1.

To prevent this from happening, Visual Basic includes a SyncLock statement that acts as a gatekeeper around a block of code. (The .NET Framework also includes other classes and features that perform a similar service.) Using SyncLock to fix the UpdateInstance() problem, you must create a common object and use it as a locking mechanism:

 Private SomeInstance As SomeClass Private LockObject As New Object Private Sub UpdateInstance(ByVal scalar As Integer)    SyncLock LockObject       SomeInstance.Member1 = 10 * scalar       SomeInstance.Member2 = 20 * scalar       SomeInstance.Member3 = 30 * scalar    End SyncLock End Sub 

As each thread enters UpdateInstance(), SyncLock TRies to exclusively lock the LockObject instance. Only when this is successful does the thread proceed through the block of code.




Visual Basic 2005 Cookbook(c) Solutions for VB 2005 Programmers
Visual Basic 2005 Cookbook: Solutions for VB 2005 Programmers (Cookbooks (OReilly))
ISBN: 0596101775
EAN: 2147483647
Year: 2006
Pages: 400

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