VB.Net Tutorial - BackgroundWorker and Invoke
Those who have worked extensively on threading will feel that this article is simple. But for who are quite new to threading and learning the Asynchronous operations, this article can be useful to figure out how to introduce the Asynchronous to the existing methods to enhance the performance.
Why there is a need of Asynchronous or Background
Without the BackgroundWorker worker, you can still build a good application. However with the help of asynchronous operations, you can improve the processing time and hence the application will be highly responsive.
Run the source code sequential
I can prove that with the example I have given in this article. If you look at the source code event btnStart_Click, I have commented out the function BgWorkerFunction. To run without asynchronous you may comment out the BackgroundWorker1.RunWorkerAsync() function call and uncomment the BgWorkerFunction call. Now you can notice that the first ListBox is loaded fully before the second ListBox started loading. It shows that the operations are sequential.
Screen Capture

Run with the BackgroundWorker
Now uncomment the BackgroundWorker1.RunWorkerAsync() and comment out the BgWorkerFunction call in the event btnStart_Click. You can see that both the ListBoxes will be loaded concurrently. So user can see the progress on two different but related operations in parallel.
Screen Capture

Start BackgroundWorker
BackgroundWorker is a UI control in the toolbox. So you can drag that from toolbox to the windows form. Before start using it you may need to specify few properties. WorkerReportsProgress to true if you need to show the progress to user. You may use the event ProgressChanged Event. If everything is configured, calling the RunWorkerAsync will initiate the process.
BackgroundWorker.DoWork
The actual operations that have to be run in background have to be written here. Keep in mind this is going to run in another thread NOT in the main thread. So we need to make the necessary invoke calls if the cross thread communications are necessary. You may use the Invoke method, and InvokeRequired property from all the (if not most) controls. Note: Intellisense won’t show these out of the box, you may need to type few letters then Intellisense will list that out.
Access the Listbox from BackgroundWorker using Invoke
As I have explained earlier, it is necessary to use invoke when there is a need for cross thread communications. In our case the ListBox is in the main thread and BackgroundWorker runs the operations in another thread. When you are running the function BgWorkerFunction from btnStart_Click event, there is no need for invoke required. But when the same function is being called from the event BackgroundWorker.DoWork, there is a need of Invoke. This can be detected in the runtime using the property InvokeRequred. This property is available in every control in the toolbox (refer the previous section on this for details).
Delegates for Invoke
The invoke function needs a delegate for accessing the control in the main thread. So I have created a delegate named as AddItemsToListBoxDelegate with same signature as my target method AddItemsToListBox. Now the AddItemsToListBox expects two parameters ToListBox, and AddText, so you need to pass these two values when using the delegate for invoking the method AddItemsToListBox. This can be achieved by the overloaded Invoke which accepts parameters to the target method as an object array. With this you can use the BackgroundWorker to invoke the controls in the main thread.
Source Code
Form((.Vb)
Imports System.ComponentModel
Public Class Form1
Public Delegate Sub AddItemsToListBoxDelegate( _
ByVal ToListBox As ListBox, _
ByVal AddText As String)
Private Sub btnStart_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnStart.Click
BackgroundWorker1.RunWorkerAsync()
MainFunction()
'BgWorkerFunction()
End Sub
Private Sub MainFunction()
Dim StartDate = Now
Dim Counter = 0
Do Until Counter > 20
AddItemsToListBox(lstMainFunction, "Current Index is " & Counter)
Counter += 1
Threading.Thread.Sleep(100)
Application.DoEvents() 'if you dont instruct App to doevents,
'you will not see any background work, the work looks sequential.
Loop
End Sub
Private Sub BgWorkerFunction()
Dim StartDate = Now
Dim Counter = 0
Do Until Counter > 20
'if you call this from a thread which is not the main/form then
'invoke is required.
If (lstBgWorker.InvokeRequired) Then
lstBgWorker.Invoke( _
New AddItemsToListBoxDelegate(AddressOf AddItemsToListBox), _
New Object() {lstBgWorker, "Current Index is " & Counter})
Else
lstBgWorker.Items.Add("Current Index is " & Counter)
lstBgWorker.SelectedIndex = lstBgWorker.Items.Count - 1
End If
Counter += 1
Threading.Thread.Sleep(100)
Application.DoEvents()
Loop
End Sub
Private Sub AddItemsToListBox(ByVal ToListBox As ListBox, _
ByVal AddText As String)
ToListBox.Items.Add(AddText)
ToListBox.SelectedIndex = ToListBox.Items.Count - 1
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
BgWorkerFunction()
End Sub
End Class
Designer(Designer.vb)
Imports System.Drawing
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.BackgroundWorker1 = New System.ComponentModel.BackgroundWorker
Me.lstMainFunction = New System.Windows.Forms.ListBox
Me.lstBgWorker = New System.Windows.Forms.ListBox
Me.lblMainFunction = New System.Windows.Forms.Label
Me.lblBgWorker = New System.Windows.Forms.Label
Me.btnStart = New System.Windows.Forms.Button
Me.SuspendLayout()
'
'BackgroundWorker1
'
Me.BackgroundWorker1.WorkerReportsProgress = True
'
'lstMainFunction
'
Me.lstMainFunction.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lstMainFunction.FormattingEnabled = True
Me.lstMainFunction.ItemHeight = 16
Me.lstMainFunction.Location = New Point(2, 56)
Me.lstMainFunction.Name = "lstMainFunction"
Me.lstMainFunction.Size = New Size(137, 340)
Me.lstMainFunction.TabIndex = 0
'
'lstBgWorker
'
Me.lstBgWorker.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lstBgWorker.FormattingEnabled = True
Me.lstBgWorker.ItemHeight = 16
Me.lstBgWorker.Location = New Point(158, 56)
Me.lstBgWorker.Name = "lstBgWorker"
Me.lstBgWorker.Size = New Size(137, 340)
Me.lstBgWorker.TabIndex = 1
'
'lblMainFunction
'
Me.lblMainFunction.AutoSize = True
Me.lblMainFunction.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lblMainFunction.Location = New Point(-1, 40)
Me.lblMainFunction.Name = "lblMainFunction"
Me.lblMainFunction.Size = New Size(66, 16)
Me.lblMainFunction.TabIndex = 2
Me.lblMainFunction.Text = "Function"
'
'lblBgWorker
'
Me.lblBgWorker.AutoSize = True
Me.lblBgWorker.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.lblBgWorker.Location = New Point(160, 39)
Me.lblBgWorker.Name = "lblBgWorker"
Me.lblBgWorker.Size = New Size(145, 16)
Me.lblBgWorker.TabIndex = 3
Me.lblBgWorker.Text = "Background Worker"
'
'btnStart
'
Me.btnStart.Font = New Font("Microsoft Sans Serif", 9.75!, _
FontStyle.Bold, _
GraphicsUnit.Point, _
CType(0, Byte))
Me.btnStart.Location = New Point(2, 12)
Me.btnStart.Name = "btnStart"
Me.btnStart.Size = New Size(75, 23)
Me.btnStart.TabIndex = 4
Me.btnStart.Text = "Start"
Me.btnStart.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New Size(305, 397)
Me.Controls.Add(Me.btnStart)
Me.Controls.Add(Me.lblBgWorker)
Me.Controls.Add(Me.lblMainFunction)
Me.Controls.Add(Me.lstBgWorker)
Me.Controls.Add(Me.lstMainFunction)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub
Friend WithEvents BackgroundWorker1 As System.ComponentModel.BackgroundWorker
Friend WithEvents lstMainFunction As System.Windows.Forms.ListBox
Friend WithEvents lstBgWorker As System.Windows.Forms.ListBox
Friend WithEvents lblMainFunction As System.Windows.Forms.Label
Friend WithEvents lblBgWorker As System.Windows.Forms.Label
Friend WithEvents btnStart As System.Windows.Forms.Button
End Class