Safe Multithreading with the BackgroundWorker Component

In the physical world, two workers are twice as productive as one. In the programming world, using additional threads safely can increase an application's productivity and add a depth and richness that help the user to be more productive too. The drawback has been that multi-threaded applications typically have been harder to write and debug. This is still true to some extent; but with .NET, multithreading is getting easier to use.

Early editions of .NET introduced thread pools, and .NET 2.0 introduces the BackgroundWorker component. The BackgroundWorker component permits you to incorporate additional worker threads, and it isn't much harder to use then the old standby, the Timer component.

This article demonstrates how to use the BackgroundWorker component safely, including how to marshal control from the worker thread back to the Windows Forms thread.

Implementing the DoWorkEventHandler

Because the BackgroundWorker class is a component, you can drag and drop it onto a Windows Form or UserControl and wire up event handlers visually. If you don't have a form or UserControl, you can declare and create an instance of the BackgroundWorker and bind its event properties with code.

The BackgroundWorker component is defined in the System.ComponentModel namespace. To use the BackgroundWorker, you can add an Imports statement or use the namespace in the declaration and initialization statement. Here is an example:

Dim worker as System.ComponentModel.BackgroundWorker
worker  = new System.ComponentModel.BackgroundWorker

The worker thread is expressed as a delegate wired to the BackgroundWorker.DoWork event property. The following is the signature of the delegate (event handler) for the DoWork property:

Sub DoWork(Sender As Object, _
           e As System.ComponentModel.DoWorkEventArgs)

Any subroutine with the two argument types in the order shown can be assigned to the DoWork event property. The BackgroundWorker component also has a ProgressChanged event property, and you can signal the relative progress of the background thread by wiring an event handler to the ProgressChanged event. The following is the signature for ProgressChanged:

Sub ProgressChanged(sender As Object, _
   e As System.ComponentModel.ProgressChangedEventArgs)

Listing 1 shows how to create an instance of the BackgroundWorker component and wire up DoWork and ProgressChanged in the Form's OnLoad event.

Listing 1: Create and Wire Up DoWork and ProgressChanged Events for BackgroundWorker Component

Private worker As BackgroundWorker

Private Sub Form1_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

   worker = New BackgroundWorker()
   worker.WorkerReportsProgress = True
   AddHandler worker.DoWork, New _
      DoWorkEventHandler(AddressOf OnWork)
   AddHandler worker.ProgressChanged, _
      New ProgressChangedEventHandler(AddressOf OnProgressChanged)

End Sub

Running the Worker Thread

To start the worker thread, you invoke the BackgroundWorker.RunWorkerAsync method. This example adds ProgressBar to a form and uses a button to simulate an event that initiates the background worker process. The following snippet demonstrates how to start the background thread:

Private Sub Button1_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles Button1.Click
   worker.RunWorkerAsync()
End Sub

Figure 1 shows the simple example Form. The ProgressBar shown will be updated indirectly by an event the BackgroundWorker component raises.

Figure 1: Example Form with ProgressBar

Safe Multithreading in Windows Forms

At this point, you have enough code to use multithreading. To interact with Windows Forms controls and control properties without crashing the program, however, you have to use delegates a bit more.

Presently, .NET is not designed to safely and reliably manipulate Windows Forms controls across thread boundaries. To correct this deficiency, you have to marshal any data or interactions from the background thread to the same thread on which the Windows Forms and controls are. You do this with a delegate. For example, if you want to update the ProgressBar from the BackgroundWorker's ProgressChanged event handler, you need to define a delegate with arguments mirroring the data you want to pass and use the Form's Invoke method. Call Invoke with the delegate address and data—this is referred to as marshalling.

Listing 2 shows the implementation of BackgroundWorker's ProgressChanged event handler, as well as the delegate definition and the additional event handler used to update the ProgressBar. OnProgressChanged responds to the ProgressChanged event and the Invoke method call uses the custom ChangeProgressBarHandler to marshal data to the Form's thread.

Listing 2: Implementation of BackgroundWorker's ProgressChanged Event Handler

Private Sub OnProgressChanged(ByVal sender As Object, _
   ByVal e As ProgressChangedEventArgs)
   Invoke(New ChangeProgressBarHandler( _
      AddressOf ChangeProgressbar), e.ProgressPercentage)
End Sub

Private Delegate Sub ChangeProgressBarHandler(ByVal percentage _
                                              As Integer)

Private Sub ChangeProgressBar(ByVal percentage As Integer)
   ProgressBar1.Value = percentage
End Sub

Listing 3 provides the complete code set for the example, showing the OnWork event handler that represents background work.

Listing 3: All the Custom Code for the Sample Form Shown in Figure 1

Imports System.ComponentModel

Public Class Form1

   Private worker As BackgroundWorker

   Private Sub Form1_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load

      worker = New BackgroundWorker()
      worker.WorkerReportsProgress = True
      AddHandler worker.DoWork, New _
         DoWorkEventHandler(AddressOf OnWork)
      AddHandler worker.ProgressChanged, _
         New ProgressChangedEventHandler(AddressOf OnProgressChanged)
   End Sub

   Private Sub OnWork(ByVal sender As Object, _
                      ByVal e As DoWorkEventArgs)
      Dim I As Integer
      For I = 1 To 100
         System.Threading.Thread.Sleep(10)
         worker.ReportProgress(I)
      Next
   End Sub

   Private Sub OnProgressChanged(ByVal sender As Object, _
      ByVal e As ProgressChangedEventArgs)
      Invoke(New ChangeProgressBarHandler( _
         AddressOf ChangeProgressbar), e.ProgressPercentage)
   End Sub

   Private Delegate Sub ChangeProgressBarHandler(ByVal _
      percentage As Integer)

   Private Sub ChangeProgressBar(ByVal percentage As Integer)
      ProgressBar1.Value = percentage
   End Sub

   Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
      worker.RunWorkerAsync()
   End Sub
End Class

OnWork really only shows that the ProgressBar is being updated, and interacting with the Form will clearly demonstrate that the Form's thread is available—for example, the form is refreshed if you move it around. That said, the sample application does demonstrate all of the preparation and steps necessary to use additional threads. All that remains for you to do is find useful work for the background thread.

Tip: If you want to see the various threads the sample application in Listing 3 uses, set a breakpoint in the code. When the breakpoint is reached, select Debug|Windows|Threads.

Delegates Are Our Friends

I don't recommend writing code that interacts with Windows Forms controls or the form itself in event handlers that are raised by asynchronous processes, threads from the thread pool, or the BackgroundWorker component. Sometimes, doing so seems to work, but cross-threaded control interaction will eventually crash your program. You can make these interactions inherently thread safe, though—I believe Delphi's Visual Control Library (VCL, the equivalent of the .NET Framework) is thread safe, but Delphi's VCL is a more mature framework.

Until the .NET Framework controls are thread safe, you will have to use Control.Invoke and delegates to marshal data from background worker threads to the Form thread. However, once you master delegates, using them and marshaling data across threads is safe and relatively easy.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object oriented programming and .NET. Look for his upcoming book UML DeMystified from McGraw-Hill/Osborne (October 2005) and C# Express from Addison Wesley (Spring 2006). You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2005 by Paul T. Kimmel. All Rights Reserved.



Comments

  • Question

    Posted by wheelibin on 01/11/2006 11:37am

    Excellent article, I am slightly confused on the delegate concept though. I am trying to populate a datatable in the seperate thread, then when it's finished, bind the datatable to a datagrid on the form. How would I pass the datatable from the seperate thread to the "main" thread?. Thanks

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Where the business performance of their mobile app portfolios are concerned, most companies are flying blind. While traditional application portfolios are held to all kinds of ROI measure, the investment plan for mobile apps -- increasingly the more crucial bet -- is made by guesswork and dart-throwing. This interactive e-book investigates how mobile is driving the need for app and portfolio measures unlike any we saw in the days of web. Good mobile analytics must deliver leading indicators of user experience …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds