logo VB.Net - Background Worker
Guide Contents
DigitalDan Home Page

Background Worker
There are occassions in programming when you have no choice, you have put computer intensive code inside a loop. Everything appears to work in test mode put strange error messages e.g. "... non pumping wait ..." crash all compiles versions of your program.
 
Windows has to share computer resources between every program, service and driver. If it allowed your program to grab resources, nothing else would work and your computer would grind to a halt. When gaced with this problem, Windows will protect itself, (any other programs,) by stopping your program.
 
For many programs, the answer is tell Windows that the program is expected to run for long time and allow it to run quietly in the background. Background programs can share resources without stopping other apps. Windows will allow background programs to run for "as long as is required. Fortunately, VB.Net has a special control called Background Worker, which allows your problem code to run in the background.
 
Simplified summary- If your program takes too long to run (or refuses to let go of resources need by other programs,) you will probably require a Background-Worker control.
 
Background Worker
Using this control requires an understanding of several new concepts. If I only offered a complete working example, it would be very difficult for a novice to understand. For this reason, I will build the code, step-by-step, introducing new ideas one-at-a-time.
 
First open a new "windows forms" application in the usual way. Then add the controls shown in the diagram.
Form for testing Background Worker
Our new Form should be called Form1, the two buttons are called Button1 and Button2, tha label will be called Label1
We also need to add a Background-Worker control. You will find this in the same toolbox as the buttons and label. The new control will appear below the form (becasue it is not normally visible) and will have to be called BackgroundWorker1.
 
We will now write a function to test our Backgroundworker. The only requirement is that it runs slowly (we need time to see what is happening when we test the program.)
 
Public Class Form1
Private Function SlowTask() as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
Next
Finished = True
Return Finished
End Function
End Class

 
The next step is to use the Backgroundworker1 control to run our SlowTask in the background. We will use Button1 to call BackgroundWorker1. (Existing code is shown in black, the new code is shown in green.)
 
Public Class Form1
Private Function SlowTask() as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SlowTask()
End Sub
End Class

 
Although our SlowTask() will now run in the background whenever we click Button1, we cannot see what is happening. We must now add code to tell us when the Backgroundworker1 has finished.
 
Public Class Form1
Private Function SlowTask() as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SlowTask()
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.text="Backgroundworker Completed"
End Sub
End Class

 
If we tried to run this code, it would appear as if nothing has happened until it suddenly announced that it has finished. It the real world, users want to see that something is happening. In order to achieve this, we must do two things, firstly, before teh background worker starts, we must warn VB.Net that BackgroundWorker1 will try to report it's progress. We will also need to update Label1.Text with the current program status. In order to report progress, we have to leave the background, display out message and then return to teh background worker. This is acheived using .ReportProgress and the .ProgressChanged event
NOTE
For simplicity, this example updates Label1.text on every padd of the For loop. You should only do this if you know each pass will take a few secondsto complete. A short loop could request so many text chages that they are queued up. These progress reports could still be arriving after the main background worker has finished.
To get round this issue, you could only request an update on every 100th or 1000th loop.
If LoopCounter Mod 1000 = 0 Then BackgroundWorker1.ReportProgress(0,"......")
 
Public Class Form1
Private Function SlowTask() as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
BackgroundWorker1.ReportProgress(0, "Loop " & i.toString())
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True BackgroundWorker1.RunWorkerAsync()
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SlowTask()
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.text="Backgroundworker Completed"
End Sub
'
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message
LblProgress.Refresh()
End Sub
End Class

 
Depending on our program logic, the BackgroundWorker1 could take a very long time (I have seen programs requiring 24 hours to complete.) What happens when the user needs to stop the program without waiting for it to finish? Fortunately, VB.Net incorporates a CancelAsync command which can be triggered by the main program. We need to tell VB.Net that we may need to stop the backgroundworker before the process starts using a WorkerSupportsCancellation instruction. For this example, we will use Button2 on Form1 to stop the backgroundworker.
 
Public Class Form1
Private Function SlowTask() as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
BackgroundWorker1.ReportProgress(0, "Loop " & i.toString())
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
If BackgroundWorker1.CancellationPending Then Exit For
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True BackgroundWorker1.WorkerSupportsCancellation = True BackgroundWorker1.RunWorkerAsync()
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SlowTask()
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.text="Backgroundworker Completed"
End Sub
'
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message
LblProgress.Refresh()
End Sub
'
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
BackgroundWorker1.CancelAsync()
End Sub
End Class

 
Our next problem is how to pass parameters to BackgroundWorker1 from the main program. If you try to do this after the Background-Worker has started, you are likely to encounter errors. (The background-worker runs separately to the main program, it could even run on a different processor.) The easiest solution is to gather all the reuired parameters before starting BackgroundWorker1. You can then pass your parameters inside the RunWorkerAsync command. (If you require more than one Parameter, it is acceptable to pass a Structure variable.)
 
I will now add code to "pass a parameter" to our background worker. To see the change, delete the line in red and replace it with the line in green.
 
Public Class Form1
Private Function SlowTask(par1 As String) as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
BackgroundWorker1.ReportProgress(0, "Loop " & i.toString())
BackgroundWorker1.ReportProgress(0, par1 & " " & i.toString())
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
If BackgroundWorker1.CancellationPending Then Exit For
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True BackgroundWorker1.WorkerSupportsCancellation = True Dim parm1 as String = "New Progress Messaage "
BackgroundWorker1.RunWorkerAsync(parm1)
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim par1 As String = e.Argument.ToString
SlowTask(par1)
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.text="Backgroundworker Completed"
End Sub
'
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message
LblProgress.Refresh()
End Sub
'
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
BackgroundWorker1.CancelAsync()
End Sub
End Class

 
We also need to be able to pass parameters back from BackgroundWorker1 to the RunWorkerCompleted subroutine. The RunWorkerCompleted is triggered after the backgroundworker completes and it is part of the main program. Vb.Net allows us to return a value (or structure) using e.Result. and we can retureve it in e.UserState
One catch, when retriving UserState, you must specify what type of variable is expected, you can use any type of variable or structue but you mist say what is expected e.g. Dim s As String = e.UserState.toString
Lets add a return parameter to our example
 
Public Class Form1
Private Function SlowTask(par1 As String) as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
BackgroundWorker1.ReportProgress(0, par1 & " " & i.toString())
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
If BackgroundWorker1.CancellationPending Then Exit For
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.WorkerReportsProgress = True BackgroundWorker1.WorkerSupportsCancellation = True Dim parm1 as String = "New Progress Messaage "
BackgroundWorker1.RunWorkerAsync(parm1)
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim par1 As String = e.Argument.ToString
SlowTask(par1)
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Label1.text="Backgroundworker Completed"
End Sub
'
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message
LblProgress.Refresh()
End Sub
'
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
BackgroundWorker1.CancelAsync()
End Sub
End Class

 
The final task is the enabling/disabling of Button1 and Button2. Every time button1 is clicked, it starts another background process. Whilst Button2 should stop BackgrouneWorker1, you cannot stop a process unless it is currently running.
 
Public Class Form1
Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles Me.Load
Button1.Enabled = True
Button2.Enabled = False
End Sub
'
Private Function SlowTask(par1 As String) as Boolean
Dim Finished as Boolean = False
For i As Integer = 1 to 20
BackgroundWorker1.ReportProgress(0, par1 & " " & i.toString())
Thread.Sleep(500)
' above line asks computer to wait half a second before continuing
If BackgroundWorker1.CancellationPending Then Exit For
Next
Finished = True
Return Finished
End Function
'
Private Sub BtnStart_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
BackgroundWorker1.WorkerReportsProgress = True BackgroundWorker1.WorkerSupportsCancellation = True Dim parm1 as String = "New Progress Messaage "
Button2.Enabled = True
BackgroundWorker1.RunWorkerAsync(parm1)
End Sub
'
Private Sub BgwMain_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim par1 As String = e.Argument.ToString
SlowTask(par1)
End Sub
'
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Button2.Enabled = False
Label1.text="Backgroundworker Completed"
Button1.Enabled = True
End Sub
'
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message
LblProgress.Refresh()
End Sub
'
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
BackgroundWorker1.CancelAsync()
End Sub
End Class

 

BackgroundWorker - Use a Timer to Report Progress
I sometimes encouter programming queries relating to the viability of writing a Background process which reports progress at fixed time intervals. i.e. every few seconds rather than every few program loops. The following code demonstrates one approach to achieve this goal. It is intended for users who ahave already used Background-workers and teh code would need tuning to individual program requirements. The code is designed to run with a standard Windows Form containing the following controls
 
Friend Shared message1 As String
Friend Shared field As New Object ' used by SyncLock to stop multiple processes accessing message1 simlutaneously
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' init Bgw
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.WorkerSupportsCancellation = False ' InitTimer
Timer1.Interval = 1300
' 1300 ms = trigger about every 1.3 seconds
Timer1.Start()
' Initialise the messafe for ReportProgress
SyncLock field
message1 = "Starting Background Worker"
End SyncLock
' Start BackgroundWorker
BackgroundWorker1.RunWorkerAsync()
End Sub
'
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
BackgroundWorker1.ReportProgress(0, "") ' ReportPrograss now reports the string in message1
Timer1.Start()
' replace everything between For and Next with your background worker code
' Set message1 to string to be reported by modified BGW progress changed event.
' You must use Synclock/End Syslock whenever message1 accessed but keep code inside SysLock/End SysLock to absolute minimum
' BGW report progress is now controlled by the timer tick event and picks up message1 regardless of ReportProgress parameters
For i As Integer = 0 To 20
System.Threading.Thread.Sleep(500)
' thread.sleep used to mimic a scenario where code takes a long time to process
SyncLock field
message1 = "Loop = " & i.ToString
' You must update message1 frequently (e.g. each pass of a loop) - user may expect value to change after most timer ticks
End SyncLock
Next
SyncLock field
message1 = "Finished Background Worker"
End SyncLock
BackgroundWorker1.ReportProgress(100, "")
' note - ReportPrograss reports contents of the string message1
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Timer1.Stop()
End Sub
Private Sub BgwTimer_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim temp As String = ""
SyncLock field
temp = message1
End SyncLock
BackgroundWorker1.ReportProgress(2, temp)
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
LblProgress.Text = message1
LblProgress.Refresh()
End Sub

DigitalDan.co.uk ... Hits = 304