The following code example is a complete Windows Forms application that consists of a form with three buttons and one text box. The first button demonstrates unsafe cross-thread access, the second button demonstrates safe access by using Invoke, and the third button demonstrates safe access by using BackgroundWorker.
Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms
Public Class Form1
Inherits Form
' This delegate enables asynchronous calls for setting
' the text property on a TextBox control.
Delegate Sub SetTextCallback(ByVal [text] As String)
' This thread is used to demonstrate both thread-safe and
' unsafe ways to call a Windows Forms control.
Private demoThread As Thread = Nothing
' This BackgroundWorker is used to demonstrate the
' preferred way of performing asynchronous operations.
Private WithEvents backgroundWorker1 As BackgroundWorker
Private textBox1 As TextBox
Private WithEvents setTextUnsafeBtn As Button
Private WithEvents setTextSafeBtn As Button
Private WithEvents setTextBackgroundWorkerBtn As Button
Private components As System.ComponentModel.IContainer = Nothing
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso (components IsNot Nothing) Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
' This event handler creates a background thread that attempts
' to set a Windows Forms control property directly.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
' Create a background thread and start it.
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
' Continue in the main thread. Set a textbox value that
' would be overwritten by demoThread if it succeeded.
' This value will appear immediately, then two seconds
' later the background thread will try to make its
' change to the textbox.
textBox1.Text = "Written by the main thread."
End Sub
' This method is executed on the worker thread. It
' attempts to access the TextBox control directly,
' which is not safe.
Private Sub ThreadProcUnsafe()
' Wait two seconds to simulate some background work
' being done.
Thread.Sleep(2000)
textBox1.Text = "Written unsafely by the background thread."
End Sub
' This event handler creates a thread that calls a
' Windows Forms control in a thread-safe way.
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) _
Handles setTextSafeBtn.Click
' Create a background thread and start it.
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
' Continue in the main thread. Set a textbox value
' that will be overwritten by demoThread.
textBox1.Text = "Written by the main thread."
End Sub
' If the calling thread is different from the thread that
' created the TextBox control, this method passes in the
' the SetText method to the SetTextCallback delegate and
' passes in the delegate to the Invoke method.
Private Sub ThreadProcSafe()
' Wait two seconds to simulate some background work
' being done.
Thread.Sleep(2000)
Dim NewText As String = "Written by the background thread."
' Check if this method is running on a different thread
' than the thread that created the control.
If Me.textBox1.InvokeRequired Then
' It's on a different thread, so use Invoke.
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[NewText] + " (Invoke)"})
Else
' It's on the same thread, no need for Invoke.
Me.textBox1.Text = [NewText] + " (No Invoke)"
End If
End Sub
' This method is passed in to the SetTextCallBack delegate
' to set the Text property of textBox1.
Private Sub SetText(ByVal [text] As String)
Me.textBox1.Text = [text]
End Sub
' This method starts BackgroundWorker by calling
' RunWorkerAsync. The Text property of the TextBox control
' is set by a method running in the main thread
' when BackgroundWorker raises the RunWorkerCompleted event.
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) _
Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
' Continue in the main thread.
textBox1.Text = "Written by the main thread."
End Sub
' This method does the work you want done in the background.
Private Sub backgroundWorker1_DoWork( _
ByVal sender As Object, _
ByVal e As DoWorkEventArgs) _
Handles backgroundWorker1.DoWork
' Wait two seconds to simulate some background work
' being done.
Thread.Sleep(2000)
' You could use the same technique as in the
' ThreadProcSafe method to set textBox1.Text here, but
' the preferred method is to do it from the Completed
' event handler which runs in the same thread as the one
' that created the control.
End Sub
' This method is called by BackgroundWorker's
' RunWorkerCompleted event. Because it runs in the
' main thread, it can safely set textBox1.Text.
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"Written by the main thread after the background thread completed."
End Sub
#Region "Windows Form Designer generated code"
Private Sub InitializeComponent()
Me.textBox1 = New System.Windows.Forms.TextBox()
Me.setTextUnsafeBtn = New System.Windows.Forms.Button()
Me.setTextSafeBtn = New System.Windows.Forms.Button()
Me.setTextBackgroundWorkerBtn = New System.Windows.Forms.Button()
Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker()
Me.SuspendLayout()
'
' textBox1
'
Me.textBox1.Location = New System.Drawing.Point(12, 12)
Me.textBox1.Name = "textBox1"
Me.textBox1.Size = New System.Drawing.Size(360, 20)
Me.textBox1.TabIndex = 0
'
' setTextUnsafeBtn
'
Me.setTextUnsafeBtn.Location = New System.Drawing.Point(15, 55)
Me.setTextUnsafeBtn.Name = "setTextUnsafeBtn"
Me.setTextUnsafeBtn.TabIndex = 1
Me.setTextUnsafeBtn.Text = "Unsafe Call"
'
' setTextSafeBtn
'
Me.setTextSafeBtn.Location = New System.Drawing.Point(96, 55)
Me.setTextSafeBtn.Name = "setTextSafeBtn"
Me.setTextSafeBtn.TabIndex = 2
Me.setTextSafeBtn.Text = "Safe Call"
'
' setTextBackgroundWorkerBtn
'
Me.setTextBackgroundWorkerBtn.Location = New System.Drawing.Point(177, 55)
Me.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"
Me.setTextBackgroundWorkerBtn.TabIndex = 3
Me.setTextBackgroundWorkerBtn.Text = "Safe BW Call"
'
' backgroundWorker1
'
'
' Form1
'
Me.ClientSize = New System.Drawing.Size(388, 96)
Me.Controls.Add(setTextBackgroundWorkerBtn)
Me.Controls.Add(setTextSafeBtn)
Me.Controls.Add(setTextUnsafeBtn)
Me.Controls.Add(textBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub 'InitializeComponent
#End Region
<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.Run(New Form1())
End Sub
End Class
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadDemo
{
public class Form1 : Form
{
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetTextCallback(string text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
private Thread demoThread = null;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;
private TextBox textBox1;
private Button setTextUnsafeBtn;
private Button setTextSafeBtn;
private Button setTextBackgroundWorkerBtn;
private System.ComponentModel.IContainer components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
// This event handler creates a background thread that
// attempts to set a Windows Forms control property
// directly.
private void setTextUnsafeBtn_Click
(object sender, EventArgs e)
{
// Create a background thread and start it.
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
// Continue in the main thread. Set a textbox value that
// would be overwritten by demoThread if it succeeded.
// This value will appear immediately, then two seconds
// later the background thread will try to make its
// change to the textbox.
textBox1.Text = "Written by the main thread.";
}
// This method is executed on the worker thread. It attempts
// to access the TextBox control directly, which is not safe.
private void ThreadProcUnsafe()
{
// Wait two seconds to simulate some background work
// being done.
Thread.Sleep(2000);
this.textBox1.Text =
"Written unsafely by the background thread.";
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
{
// Create a background thread and start it.
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
// Continue in the main thread. Set a textbox value
// that will be overwritten by demoThread.
textBox1.Text = "Written by the main thread.";
}
// If the calling thread is different from the thread that
// created the TextBox control, this method passes in the
// the SetText method to the SetTextCallback delegate and
// passes in the delegate to the Invoke method.
private void ThreadProcSafe()
{
// Wait two seconds to simulate some background work
// being done.
Thread.Sleep(2000);
string text = "Written by the background thread.";
// Check if this method is running on a different thread
// than the thread that created the control.
if (this.textBox1.InvokeRequired)
{
// It's on a different thread, so use Invoke.
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke
(d, new object[] { text + " (Invoke)" });
}
else
{
// It's on the same thread, no need for Invoke
this.textBox1.Text = text + " (No Invoke)";
}
}
// This method is passed in to the SetTextCallBack delegate
// to set the Text property of textBox1.
private void SetText(string text)
{
this.textBox1.Text = text;
}
// This method starts BackgroundWorker by calling
// RunWorkerAsync. The Text property of the TextBox control
// is set by a method running in the main thread
// when BackgroundWorker raises the RunWorkerCompleted event.
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
this.backgroundWorker1.RunWorkerAsync();
// Continue in the main thread.
textBox1.Text = "Written by the main thread.";
}
// This method does the work you want done in the background.
void backgroundWorker1_DoWork (object sender, DoWorkEventArgs e)
{
// Wait two seconds to simulate some background work
// being done.
Thread.Sleep(2000);
// You could use the same technique as in the
// ThreadProcSafe method to set textBox1.Text here, but
// the preferred method is to do it from the Completed
// event handler which runs in the same thread as the one
// that created the control.
}
// This method is called by BackgroundWorker's
// RunWorkerCompleted event. Because it runs in the
// main thread, it can safely set textBox1.Text.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"Written by the main thread after the background thread completed.";
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.setTextUnsafeBtn = new System.Windows.Forms.Button();
this.setTextSafeBtn = new System.Windows.Forms.Button();
this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 12);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(360, 20);
this.textBox1.TabIndex = 0;
//
// setTextUnsafeBtn
//
this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);
this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
this.setTextUnsafeBtn.TabIndex = 1;
this.setTextUnsafeBtn.Text = "Unsafe Call";
this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
//
// setTextSafeBtn
//
this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
this.setTextSafeBtn.Name = "setTextSafeBtn";
this.setTextSafeBtn.TabIndex = 2;
this.setTextSafeBtn.Text = "Safe Call";
this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
//
// setTextBackgroundWorkerBtn
//
this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);
this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
this.setTextBackgroundWorkerBtn.TabIndex = 3;
this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";
this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
//
// Form1
//
this.ClientSize = new System.Drawing.Size(388, 96);
this.Controls.Add(this.setTextBackgroundWorkerBtn);
this.Controls.Add(this.setTextSafeBtn);
this.Controls.Add(this.setTextUnsafeBtn);
this.Controls.Add(this.textBox1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
}
When you run the application and click the Unsafe Call button, you immediately see "Written by the main thread" in the text box. Two seconds later, when the unsafe call is attempted, the Visual Studio debugger indicates that an exception occurred. The debugger stops at the line in the background thread that attempted to write directly to the text box. You will have to restart the application to test the other two buttons. When you click the Safe Call button, "Written by the main thread" appears in the text box. Two seconds later, the text box is set to "Written by the background thread (Invoke)", which indicates that the Invoke method was called. When you click the Safe BW Call button, "Written by the main thread" appears in the text box. Two seconds later, the text box is set to "Written by the main thread after the background thread completed", which indicates that the handler for the RunWorkerCompleted event of BackgroundWorker was called.