Threading in Windows Forms (VB)
Overview
Windows Forms controls are bound to a single UI thread. Performing long‑running operations on this thread blocks the UI, causing the application to become unresponsive. Proper threading techniques allow you to keep the UI responsive while executing background work.
Using BackgroundWorker
The BackgroundWorker
component simplifies executing code on a separate thread and reporting progress back to the UI.
Imports System.ComponentModel
Public Class Form1
Private WithEvents bw As New BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
End Sub
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
If Not bw.IsBusy Then bw.RunWorkerAsync()
End Sub
Private Sub bw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bw.DoWork
For i As Integer = 1 To 100
If bw.CancellationPending Then
e.Cancel = True
Exit For
End If
System.Threading.Thread.Sleep(50) ' Simulate work
bw.ReportProgress(i)
Next
End Sub
Private Sub bw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bw.ProgressChanged
progressBar.Value = e.ProgressPercentage
lblStatus.Text = $"Progress: {e.ProgressPercentage}%"
End Sub
Private Sub bw_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
If e.Cancelled Then
lblStatus.Text = "Cancelled"
ElseIf e.Error IsNot Nothing Then
lblStatus.Text = $"Error: {e.Error.Message}"
Else
lblStatus.Text = "Completed!"
End If
End Sub
End Class
Using Task
and Await
For .NET 4.5+ you can leverage Task
with async/await to keep code concise.
Imports System.Threading.Tasks
Public Class Form1
Private Async Sub btnLoadData_Click(sender As Object, e As EventArgs) Handles btnLoadData.Click
btnLoadData.Enabled = False
progressBar.Style = ProgressBarStyle.Marquee
Try
Dim result As String = Await Task.Run(Function()
' Simulate long work
System.Threading.Thread.Sleep(2000)
Return "Data loaded"
End Function)
lblResult.Text = result
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
btnLoadData.Enabled = True
progressBar.Style = ProgressBarStyle.Blocks
End Try
End Sub
End Class
Invoking the UI Thread
If you must update a control from a background thread, use Control.Invoke
(or BeginInvoke
).
Private Sub DoWork()
' Running on a background thread
Dim text As String = GetData()
If lblResult.InvokeRequired Then
lblResult.Invoke(Sub() lblResult.Text = text)
Else
lblResult.Text = text
End If
End Sub
Best Practices
Scenario | Recommended Approach |
---|---|
Simple background work with progress reporting | BackgroundWorker |
Modern async code, .NET 4.5+ | Task + async/await |
Need to update UI from non‑UI thread | Control.Invoke/BeginInvoke or SynchronizationContext |
Cancellation support | CancellationTokenSource with Task |