How to use BackGroundWorker to Create Threads

Reverend Jim 3 Tallied Votes 4K Views Share

Sometimes there are functions you want your program to perform in the background while you are busy doing other things. For example, if you are building a picture viewer application you may not want to wait until thumbnails are generated before getting on with the business of viewing the pictures. If you have ever used the FastStone Image Viewer you will have noticed the progress bar at the bottom of the screen when browsing to a new folder. That bar shows the progress of a background thread which is busy scanning the current folder. If you try to change the sort order of files during the scan you will get the message Background thread is busy scanning the current folder. Try again later.

Implementing background threads can be easily done (but with a couple of gotchas). I hope this tutorial will be enough to get you started.

Development Environment: vb.net 2010
Development Platform: Windows 7 Pro (32 bit)

The following form will be used in the tutorial:

The objects are as follows:

frmBGTest   the main form

    .Text = "BG Test"

btnStart    Button

    .Text = "Start"
    .Enabled = True

btnStop     Button

    .Text = "Stop"
    .Enabled = False

Label1      Label

    .Text = "Foreground"

Label2      Label

    .Text = "Background"

lblFGStat   Label

lblBGStat   Label

bgwThread   BackgroundWorker

prgThread   ProgressBar

    .Dock = Bottom

This example will run a background thread for 30 seconds. Progress of the thread will be shown by three different indicators. A progress bar will show the progress graphically. Two labels will be updated with numeric values. One label will be updated by the foreground (main) thread and the other by the background thread.

It is worth noting that the only code that executes in the background is the handler for the DoWork event. This is important to note for two reasons:

  1. Controls defined in the main thread cannot be modified from any other thread. Any changes to a control must be done through a delegate.
  2. Modification of other resources (user defined objects, variables, etc.) must be carefully managed to avoid deadlocks (when two threads try to change the same object at the same time). Learning how to avoid conflicts is beyond the scope of this tutorial. You can find more information on this by Googling Thread Synchronization with Semaphores.

In order for background threads to be really useful they have to have some way of communicating with the foreground thread. There are several ways this can be done.

  1. The background thread can report progress (as an integer value representing a percentage from 0 to 100).
  2. The background can signal when it has completed (this is done automatically) by raising an event.
  3. The foreground thread can check whether or not a background thread is active by calling its IsBusy method.
  4. The background thread can directly modify variables in the foreground thread.

The last method should be used with caution. You want to ensure that both threads are not trying to modify the same variable at the same time. That's the first "gotcha".

We are going to declare two things at the class level. The first is the number of seconds we want the background thread to run. The other is something called a delegate (we'll discuss that later).

'The Background thread will execute for NumSeconds seconds or until manually stopped

Private NumSeconds As Integer = 30

'The Delegate us used by the Background thread to update the Label control in the   
'main thread. Any number any type of parameters can be defined.                     

Private Delegate Sub dlgUpdateStatus(text As String)

Now let's examine the Subs that will make up our application. The first is the event handler for form load. This sets up the initial configuration. Some of the properties could easily be set at design time but by setting them in the form load I don't have to describe the objects in as great detail. We are also going to be setting and clearing the Enabled property of the buttons to ensure that they are only clicked when appropriate. The form load event looks like

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

    'Set the initial states of all controls

    btnStart.Enabled = True
    btnStop.Enabled = False

    lblFGStat.Text = "Stopped"
    lblBGStat.Text = "Stopped"

    'Set the WorkerReportsProgress property to true if you want the BackgroundWorker
    'to support progress updates. When this property is true, user code can call the
    'ReportProgress method to raise the ProgressChanged event.                      

    bgwThread.WorkerReportsProgress = True

    'Set the WorkerSupportsCancellation property to true if you want the Background 
    'Worker to support cancellation. When this property is true, you can call the   
    'CancelAsync method to interrupt a background operation.                        

    bgwThread.WorkerSupportsCancellation = True

End Sub

If your background thread does not need to report progress then you can set WorkerReportsProgress to False and just rely on the RunWorkerCompleted event to tell you when the thread is done.

Now let's look at the code behind the Start and Stop buttons.

Private Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click

    'Start the Background thread

    btnStart.Enabled = False
    btnStop.Enabled = True

    'Start the thread

    bgwThread.RunWorkerAsync()

End Sub

All that is necessary to start the background thread is to call RunWorkerAsync. Stopping the thread is a little trickier. First you want to ensure that the thread is actually active. Also, the thread has to be capable of being cancelled. This was set at form load by setting WorkerSupportsCancellation to True.

    Private Sub btnStop_Click(sender As System.Object, e As System.EventArgs) Handles btnStop.Click

        'Stop the Background thread (if it is active)

        If bgwThread.IsBusy AndAlso bgwThread.WorkerSupportsCancellation Then
            bgwThread.CancelAsync()
        End If

        btnStart.Enabled = True
        btnStop.Enabled = False

    End Sub

Before we get to the actual DoWork part, let's look at the handler to update the progress bar. The background thread reports back to the main thread by calling its ReportProgress method. The single parameter is an integer value that represents the percentage complete. ReportProgress triggers the ProgressChanged event in the main thread. We are going to do two things. We will set the Value property of the progress bar as well as the Text property of lblFGStat. For this example the code looks like

Private Sub bgwThread_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwThread.ProgressChanged

    'This event is enabled by setting the WorkerReportsProgress of the Background   
    'Worker. It is triggered when the BackgroundWorker executes the ReportProgress  
    'method with a percentage value as the parameter.                               

    prgThread.Value = e.ProgressPercentage          'update the progress bar        
    lblFGStat.Text = e.ProgressPercentage & "%"     'update the foreground label    

End Sub

The background thread will raise the RunWorkerCompleted event when it completes. All we really need to do here is update the status label and enable/disable some buttons.

Private Sub bgwThread_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwThread.RunWorkerCompleted

    'This event is triggered when the DoWork code of the BackgroundWorker ends      
    'either because is has run to completion or been cancelled.                     

    lblFGStat.Text = IIf(e.Cancelled, "Cancelled", "Completed")

    btnStart.Enabled = True
    btnStop.Enabled = False

End Sub

Before we get to the DoWork code, let's talk about the second "gotcha". If you try to modify a foreground thread control from a background thread you will get something like

Cross-thread operation not valid: Control 'lblBGStat' accessed from a thread other than the thread it was created on.

In order to modify foreground controls (again, to be done with caution) you have to use something called a delegate. This example has two status labels, lblFGStat and lblBGStat. The first is updated directly by the foreground thread. The latter is updated by the background thread using the delegate. You start by creating a Sub to do the update. The Sub will check to see if is being run in the foreground or background thread. The control's InvokeRequired returns True if it is running in a background thread. In this case you must access the control through the delegate. For the sake of clarity, I name the delegate the same as the referenced Sub by prefixing with "dlg". So the delegate for UpdateStatus is dlgUpdateStatus. The code looks like

Private Sub UpdateStatus(text As String)

    'InvokeRequired returns True if running in other than the main thread in which  
    'case the call must be made through the delegate.                               

    If lblBGStat.InvokeRequired Then
        'we are running in a Background thread
        Dim dlg As New dlgUpdateStatus(AddressOf UpdateStatus)
        Me.Invoke(dlg, text)
    Else
        'we are running in the main thread
        lblBGStat.Text = text
    End If

End Sub

You can call UpdateStatus from any thread and it will use the delegate as required. All that leaves us now is the actual code in the background thread. In this example we are using a loop. Because we want to be able to cancel the thread on demand we have to repeatedly check if a cancel has been requested. If so, all we have to do is set the e.Cancel flag and exit. If a cancel has not been requested then we trigger the ReportProgress event with a percentage completed value, and call UpdateStatus (which will use the delegate) to update lblBGStat. If we get to the End Sub then the RunWorkerCompleted even will be triggered.

Private Sub bgwThread_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwThread.DoWork

    'The DoWork Sub executes on a separate thread. If you want to modify controls on
    'the form you must do it using a delegate.                                      

    'Execute a one second loop for the given number of seconds.

    For i As Integer = 0 To NumSeconds

        'If the main thread has requested a cancellation then exit gracefully.

        If bgwThread.CancellationPending Then
            UpdateStatus("Cancelled")
            e.Cancel = True
            Exit Sub
        End If

        'Trigger the ReportProgress event with percent complete as the parameter    
        'and update the Background status label using the delegate-capable Sub.     

        bgwThread.ReportProgress(CInt((i / NumSeconds) * 100))
        UpdateStatus(CInt((i / NumSeconds) * 100).ToString & "%")

        'Sleep for one second.

        System.Threading.Thread.Sleep(1000)

    Next

    'At this point the Background thread has run to completion. Update the status   
    'using the delegate-capable Sub.                                                

    UpdateStatus("Completed")

End Sub

So that is the entire example. Just in case any code slipped through the cracks (and to make it easier to copy all the code) here is the entire code in one chunk.

'                                                                                       
'   Name:                                                                               
'                                                                                       
'       BGTest                                                                          
'                                                                                       
'   Description:                                                                        
'                                                                                       
'       This project gives an example of how to create and program a BackgroundWorker   
'       to run a thread concurrently with the main thread.                              
'                                                                                       
'   Notes:                                                                              
'                                                                                       
'       The only code that executes in the background is the handler for the DoWork     
'       event. This is important to note for two reasons:                               
'                                                                                       
'       1 - Controls defined in the main thread cannot be modified from any other       
'           thread. Any changes to a control must be done through a delegate. This      
'           sample project shows how that is done.                                      
'                                                                                       
'       2 - Management of other resources (user defined objects, variables, etc.)       
'           must be carefully managed to avoid deadlocks (when two threads try to       
'           change the same object at the same time). Learning how to avoid conflicts   
'           is beyond the scope of this example. You can find more information on this  
'           by Googling Thread Synchronization with Semaphores.                         
'                                                                                       
'   Audit:                                                                              
'                                                                                       
'       2014-04-19  rj  original code                                                   
'                                                                                       

Public Class frmBGTest

    'The Background thread will execute for NumSeconds seconds or until manually stopped

    Private NumSeconds As Integer = 30

    'The Delegate us used by the Background thread to update the Label control in the   
    'main thread. Any number any type of parameters can be defined.                     

    Private Delegate Sub dlgUpdateStatus(text As String)

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

        'Set the initial states of all controls

        btnStart.Enabled = True
        btnStop.Enabled = False

        lblFGStat.Text = "Stopped"
        lblBGStat.Text = "Stopped"

        'Set the WorkerReportsProgress property to true if you want the BackgroundWorker
        'to support progress updates. When this property is true, user code can call the
        'ReportProgress method to raise the ProgressChanged event.                      

        bgwThread.WorkerReportsProgress = True

        'Set the WorkerSupportsCancellation property to true if you want the Background 
        'Worker to support cancellation. When this property is true, you can call the   
        'CancelAsync method to interrupt a background operation.                        

        bgwThread.WorkerSupportsCancellation = True

    End Sub

    Private Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click

        'Start the Background thread

        btnStart.Enabled = False
        btnStop.Enabled = True

        'Start the thread

        bgwThread.RunWorkerAsync()

    End Sub

    Private Sub btnStop_Click(sender As System.Object, e As System.EventArgs) Handles btnStop.Click

        'Stop the Background thread (if it is active)

        If bgwThread.IsBusy AndAlso bgwThread.WorkerSupportsCancellation Then
            bgwThread.CancelAsync()
        End If

        btnStart.Enabled = True
        btnStop.Enabled = False

    End Sub

    Private Sub UpdateStatus(text As String)

        'InvokeRequired returns True if running in other than the main thread in which  
        'case the call must be made through the delegate.                               

        If lblBGStat.InvokeRequired Then
            'we are running in a Background thread
            Dim dlg As New dlgUpdateStatus(AddressOf UpdateStatus)
            Me.Invoke(dlg, text)
        Else
            'we are running in the main thread
            lblBGStat.Text = text
        End If

    End Sub

    Private Sub bgwThread_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwThread.ProgressChanged

        'This event is enabled by setting the WorkerReportsProgress of the Background   
        'Worker. It is triggered when the BackgroundWorker executes the ReportProgress  
        'method with a percentage value as the parameter.                               

        prgThread.Value = e.ProgressPercentage          'update the progress bar        
        lblFGStat.Text = e.ProgressPercentage & "%"     'update the foreground label    

    End Sub

    Private Sub bgwThread_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwThread.RunWorkerCompleted

        'This event is triggered when the DoWork code of the BackgroundWorker ends      
        'either because is has run to completion or been cancelled.                     

        lblFGStat.Text = IIf(e.Cancelled, "Cancelled", "Completed")

        btnStart.Enabled = True
        btnStop.Enabled = False

    End Sub

    Private Sub bgwThread_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwThread.DoWork

        'The DoWork Sub executes on a separate thread. If you want to modify controls on
        'the form you must do it using a delegate.                                      

        'Execute a one second loop for the given number of seconds.

        For i As Integer = 0 To NumSeconds

            'If the main thread has requested a cancellation then exit gracefully.

            If bgwThread.CancellationPending Then
                UpdateStatus("Cancelled")
                e.Cancel = True
                Exit Sub
            End If

            'Trigger the ReportProgress event with percent complete as the parameter    
            'and update the Background status label using the delegate-capable Sub.     

            bgwThread.ReportProgress(CInt((i / NumSeconds) * 100))
            UpdateStatus(CInt((i / NumSeconds) * 100).ToString & "%")

            'Sleep for one second.

            System.Threading.Thread.Sleep(1000)

        Next

        'At this point the Background thread has run to completion. Update the status   
        'using the delegate-capable Sub.                                                

        UpdateStatus("Completed")

    End Sub

End Class
3DotDev 0 Newbie Poster

use a delegate to update the UI with the backgroundworker component is useless......
why don't you use the e.UserState argument that do the same stuff ??

Reverend Jim 4,968 Hi, I'm Jim, one of DaniWeb's moderators. Moderator Featured Poster

Can you provide a more comprehensive rebuttal other than "useless"? Lacking any other info I have to disagree. I find that using the Microsoft Recommended way of updating the UI works adequately. In my IDE (VS 2010) I do not see the UserState property. I'm assuming you mean e in the DoWork handler. Again, you neglected to provide any useful information. Perhaps a working code snippet with your version would provide a useful comparison.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.