Marshalling Background Threads to Foreground Threads
If we were to report the thread being used in ProgressChanged, you would likely see that the thread ID indicated is a completely different thread. In our console application, this situation causes no problems because the static Console methods are thread-safe. But Windows Forms controls are not thread-safe. Fortunately, there is a solution.
When you’re using the BackgroundWorker component in Windows Forms, every control has an InvokeRequired property. If you check InvokeRequired and it returns True, you need to marshal the background thread’s data onto the control’s thread, using Control.Invoke and passing the address of a delegate and the data. If you don’t do this, it’s just like driving without a seatbelt: Eventually you’re going to crash, and the result won’t be pretty.
Listing 5 demonstrates how we can have a form with a progress bar control and safely update the percentage complete by using Control.Invoke.
Listing 5 Updating a progress bar from a background thread by using Invoke.
Public Class Progress Private Shared form As Progress Public Shared Sub UpdateProgress(ByVal percent As Integer) If (form Is Nothing) Then form = New Progress form.Show() form.ProgressUpdater(percent) End Sub Public Delegate Sub Updater(ByVal percent As Integer) Private Sub ProgressUpdater(ByVal percent As Integer) If (InvokeRequired) Then Invoke( _ New Updater(AddressOf ProgressUpdater), percent) ProgressBar1.Value = percent End Sub Public Shared Sub Finished() form.Close() form = Nothing End Sub Private Sub Progress_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ProgressBar1.Minimum = 1 ProgressBar1.Maximum = 100 End Sub End Class
The class Progress is a Windows Form with a progress bar control on it. All of the interesting behavior happens in the method ProgressUpdater. ProgressUpdater checks InvokeRequired; if it is required, it calls itself recursively using the Form’s Invoke method and passing itself as the target of invocation. If Invoke is not required, the progress bar is simply updated.
It’s worth noting that after we call Invoke, the InvokeRequired test should fail, and the progress bar is safely updated on the Form and ProgressBar’s thread. It’s also worth mentioning that anonymous methods were invented to reduce the number of times that event handlers need to be created simply to support a cross-thread invoke call.