Safe Multithreading with the BackgroundWorker Component, Page 2
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 datathis 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 availablefor 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, thoughI 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 firstname.lastname@example.org.
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.