If you use multithreading to improve the performance of your Windows Forms applications, you must make sure that you make calls to your controls in a thread-safe way.
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.
It is unsafe to call a control from a thread other than the one that created the control without using the Invoke method. The following is an example of a call that is not thread safe.
' This event handler creates a background thread that attempts' to set a Windows Forms control property directly.PrivateSub setTextUnsafeBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
' Create a background thread and start it.Me.demoThread = New Thread( _
New ThreadStart(AddressOfMe.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."EndSub' This method is executed on the worker thread. It' attempts to access the TextBox control directly,' which is not safe.PrivateSub ThreadProcUnsafe()
' Wait two seconds to simulate some background work' being done.
Thread.Sleep(2000)
textBox1.Text = "Written unsafely by the background thread."EndSub
// This event handler creates a background thread that // attempts to set a Windows Forms control property // directly.privatevoid 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.privatevoid 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 an unsafe way.private:
void setTextUnsafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcUnsafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes// an unsafe call on the TextBox control.private:
void ThreadProcUnsafe()
{
this->textBox1->Text = "This text was set unsafely.";
}
The .NET Framework helps you detect when you are accessing your controls in a manner that is not thread safe. When you are running your application in the debugger, and a thread other than the one which created a control tries to call that control, the debugger raises an InvalidOperationException with the message, "Control control name accessed from a thread other than the thread it was created on."
This exception occurs reliably during debugging and, under some circumstances, at run time. You might see this exception when you debug applications that you wrote with the .NET Framework prior to the .NET Framework version 2.0. You are strongly advised to fix this problem when you see it, but you can disable it by setting the CheckForIllegalCrossThreadCalls property to false. This causes your control to run like it would run under Visual Studio .NET 2003 and the .NET Framework 1.1.
Note:
If you are using ActiveX controls on a form, you may receive the cross-thread InvalidOperationException when you run under the debugger. When this occurs, the ActiveX control does not support multithreading. For more information about using ActiveX controls with Windows Forms, see Windows Forms and Unmanaged Applications. If you are using Visual Studio, you can prevent this exception by disabling the Visual Studio hosting process. For more information, see How to: Disable the Hosting Process.
Making Thread-Safe Calls to Windows Forms Controls
To make a thread-safe call to a Windows Forms control
If InvokeRequired returns true, call Invoke with a delegate that makes the actual call to the control.
If InvokeRequired returns false, call the control directly.
In the following code example, a thread-safe call is implemented in the ThreadProcSafe method, which is executed by the background thread. If the TextBox control's InvokeRequired returns true, the ThreadProcSafe method creates an instance of SetTextCallback and passes that to the form's Invoke method. This causes the SetText method to be called on the thread that created the TextBox control, and in this thread context the Text property is set directly.
' This event handler creates a thread that calls a ' Windows Forms control in a thread-safe way.PrivateSub setTextSafeBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) _
Handles setTextSafeBtn.Click
' Create a background thread and start it.Me.demoThread = New Thread( _
New ThreadStart(AddressOfMe.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."EndSub' 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.PrivateSub ThreadProcSafe()
' Wait two seconds to simulate some background work' being done.
Thread.Sleep(2000)
Dim NewText AsString = "Written by the background thread."' Check if this method is running on a different thread' than the thread that created the control.IfMe.textBox1.InvokeRequired Then' It's on a different thread, so use Invoke.Dim d AsNew SetTextCallback(AddressOf SetText)
Me.Invoke(d, NewObject() {[NewText] + " (Invoke)"})
Else' It's on the same thread, no need for Invoke.Me.textBox1.Text = [NewText] + " (No Invoke)"EndIfEndSub
// This event handler creates a thread that calls a // Windows Forms control in a thread-safe way.privatevoid 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.privatevoid 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 Invokethis.textBox1.Text = text + " (No Invoke)";
}
}
// 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)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcSafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes// a thread-safe call on the TextBox control.private:
void ThreadProcSafe()
{
this->SetText("This text was set safely.");
}
' This method is passed in to the SetTextCallBack delegate' to set the Text property of textBox1.PrivateSub SetText(ByVal [text] AsString)
Me.textBox1.Text = [text]
EndSub
// This method is passed in to the SetTextCallBack delegate// to set the Text property of textBox1.privatevoid SetText(string text)
{
this.textBox1.Text = text;
}
// This method demonstrates a pattern for making thread-safe// calls on a Windows Forms control.//// If the calling thread is different from the thread that// created the TextBox control, this method creates a// SetTextDelegate and calls itself asynchronously using the// Invoke method.//// If the calling thread is the same as the thread that created// the TextBox control, the Text property is set directly.private:
void SetText(String^ text)
{
// InvokeRequired required compares the thread ID of the// calling thread to the thread ID of the creating thread.// If these threads are different, it returns true.if (this->textBox1->InvokeRequired)
{
SetTextDelegate^ d =
gcnew SetTextDelegate(this, &Form1::SetText);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->textBox1->Text = text;
}
}
Making Thread-Safe Calls by using BackgroundWorker
The preferred way to implement multithreading in your application is to use the BackgroundWorker component. The BackgroundWorker component uses an event-driven model for multithreading. The background thread runs your DoWork event handler, and the thread that creates your controls runs your ProgressChanged and RunWorkerCompleted event handlers. You can call your controls from your ProgressChanged and RunWorkerCompleted event handlers.
To make thread-safe calls by using BackgroundWorker
Create a method to do the work that you want done in the background thread. Do not call controls created by the main thread in this method.
Create a method to report the results of your background work after it finishes. You can call controls created by the main thread in this method.
Bind the method created in step 1 to the DoWork event of an instance of BackgroundWorker, and bind the method created in step 2 to the same instance’s RunWorkerCompleted event.
In the following code example, the DoWork event handler uses Sleep to simulate work that takes some time. It does not call the form’s TextBox control. The TextBox control's Text property is set directly in the RunWorkerCompleted event handler.
' This BackgroundWorker is used to demonstrate the ' preferred way of performing asynchronous operations.PrivateWithEvents backgroundWorker1 As BackgroundWorker
// This BackgroundWorker is used to demonstrate the// preferred way of performing asynchronous operations.private:
BackgroundWorker^ backgroundWorker1;
' 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.PrivateSub setTextBackgroundWorkerBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) _
Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
' Continue in the main thread.
textBox1.Text = "Written by the main thread."EndSub' This method does the work you want done in the background.PrivateSub backgroundWorker1_DoWork( _
ByVal sender AsObject, _
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.EndSub' This method is called by BackgroundWorker's ' RunWorkerCompleted event. Because it runs in the' main thread, it can safely set textBox1.Text.PrivateSub backgroundWorker1_RunWorkerCompleted( _
ByVal sender AsObject, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"Written by the main thread after the background thread completed."EndSub
// 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.privatevoid 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.privatevoid backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"Written by the main thread after the background thread completed.";
}
// This event handler starts the form's// BackgroundWorker by calling RunWorkerAsync.//// The Text property of the TextBox control is set// when the BackgroundWorker raises the RunWorkerCompleted// event.private:
void setTextBackgroundWorkerBtn_Click(Object^ sender, EventArgs^ e)
{
this->backgroundWorker1->RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox// control. It is called on the thread that created the// TextBox control, so the call is thread-safe.//// BackgroundWorker is the preferred way to perform asynchronous// operations.private:
void backgroundWorker1_RunWorkerCompleted(
Object^ sender,
RunWorkerCompletedEventArgs^ e)
{
this->textBox1->Text =
"This text was set safely by BackgroundWorker.";
}
You can also report the progress of a background task by using the ProgressChanged event. For an example that incorporates that event, see BackgroundWorker.
Example
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
PublicClass Form1
Inherits Form
' This delegate enables asynchronous calls for setting' the text property on a TextBox control.DelegateSub SetTextCallback(ByVal [text] AsString)
' 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.PrivateWithEvents backgroundWorker1 As BackgroundWorker
Private textBox1 As TextBox
PrivateWithEvents setTextUnsafeBtn As Button
PrivateWithEvents setTextSafeBtn As Button
PrivateWithEvents setTextBackgroundWorkerBtn As Button
Private components As System.ComponentModel.IContainer = NothingPublicSubNew()
InitializeComponent()
EndSubProtectedOverridesSub Dispose(ByVal disposing AsBoolean)
If disposing AndAlso (components IsNot Nothing) Then
components.Dispose()
EndIfMyBase.Dispose(disposing)
EndSub' This event handler creates a background thread that attempts' to set a Windows Forms control property directly.PrivateSub setTextUnsafeBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
' Create a background thread and start it.Me.demoThread = New Thread( _
New ThreadStart(AddressOfMe.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."EndSub' This method is executed on the worker thread. It' attempts to access the TextBox control directly,' which is not safe.PrivateSub ThreadProcUnsafe()
' Wait two seconds to simulate some background work' being done.
Thread.Sleep(2000)
textBox1.Text = "Written unsafely by the background thread."EndSub' This event handler creates a thread that calls a ' Windows Forms control in a thread-safe way.PrivateSub setTextSafeBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) _
Handles setTextSafeBtn.Click
' Create a background thread and start it.Me.demoThread = New Thread( _
New ThreadStart(AddressOfMe.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."EndSub' 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.PrivateSub ThreadProcSafe()
' Wait two seconds to simulate some background work' being done.
Thread.Sleep(2000)
Dim NewText AsString = "Written by the background thread."' Check if this method is running on a different thread' than the thread that created the control.IfMe.textBox1.InvokeRequired Then' It's on a different thread, so use Invoke.Dim d AsNew SetTextCallback(AddressOf SetText)
Me.Invoke(d, NewObject() {[NewText] + " (Invoke)"})
Else' It's on the same thread, no need for Invoke.Me.textBox1.Text = [NewText] + " (No Invoke)"EndIfEndSub' This method is passed in to the SetTextCallBack delegate' to set the Text property of textBox1.PrivateSub SetText(ByVal [text] AsString)
Me.textBox1.Text = [text]
EndSub' 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.PrivateSub setTextBackgroundWorkerBtn_Click( _
ByVal sender AsObject, _
ByVal e As EventArgs) _
Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
' Continue in the main thread.
textBox1.Text = "Written by the main thread."EndSub' This method does the work you want done in the background.PrivateSub backgroundWorker1_DoWork( _
ByVal sender AsObject, _
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.EndSub' This method is called by BackgroundWorker's ' RunWorkerCompleted event. Because it runs in the' main thread, it can safely set textBox1.Text.PrivateSub backgroundWorker1_RunWorkerCompleted( _
ByVal sender AsObject, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"Written by the main thread after the background thread completed."EndSub
#Region "Windows Form Designer generated code"PrivateSub 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()
EndSub'InitializeComponent
#End Region
<STAThread()> _
SharedSub Main()
Application.EnableVisualStyles()
Application.Run(New Form1())
EndSubEndClass
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadDemo
{
publicclass 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.privatevoid 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.privatevoid 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.privatevoid 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.privatevoid 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 Invokethis.textBox1.Text = text + " (No Invoke)";
}
}
// This method is passed in to the SetTextCallBack delegate// to set the Text property of textBox1.privatevoid 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.privatevoid 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.privatevoid 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
privatevoid 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]
staticvoid Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
}
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
usingnamespace System;
usingnamespace System::ComponentModel;
usingnamespace System::Threading;
usingnamespace System::Windows::Forms;
namespace CrossThreadDemo
{
public ref class Form1 : public Form
{
// This delegate enables asynchronous calls for setting// the text property on a TextBox control.
delegate void SetTextDelegate(String^ text);
// This thread is used to demonstrate both thread-safe and// unsafe ways to call a Windows Forms control.private:
Thread^ demoThread;
// 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;
public:
Form1()
{
components = nullptr;
InitializeComponent();
}
protected:
~Form1()
{
if (components != nullptr)
{
delete components;
}
}
// This event handler creates a thread that calls a// Windows Forms control in an unsafe way.private:
void setTextUnsafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcUnsafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes// an unsafe call on the TextBox control.private:
void ThreadProcUnsafe()
{
this->textBox1->Text = "This text was set unsafely.";
}
// 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)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcSafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes// a thread-safe call on the TextBox control.private:
void ThreadProcSafe()
{
this->SetText("This text was set safely.");
}
// This method demonstrates a pattern for making thread-safe// calls on a Windows Forms control.//// If the calling thread is different from the thread that// created the TextBox control, this method creates a// SetTextDelegate and calls itself asynchronously using the// Invoke method.//// If the calling thread is the same as the thread that created// the TextBox control, the Text property is set directly.private:
void SetText(String^ text)
{
// InvokeRequired required compares the thread ID of the// calling thread to the thread ID of the creating thread.// If these threads are different, it returns true.if (this->textBox1->InvokeRequired)
{
SetTextDelegate^ d =
gcnew SetTextDelegate(this, &Form1::SetText);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->textBox1->Text = text;
}
}
// This event handler starts the form's// BackgroundWorker by calling RunWorkerAsync.//// The Text property of the TextBox control is set// when the BackgroundWorker raises the RunWorkerCompleted// event.private:
void setTextBackgroundWorkerBtn_Click(Object^ sender, EventArgs^ e)
{
this->backgroundWorker1->RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox// control. It is called on the thread that created the// TextBox control, so the call is thread-safe.//// BackgroundWorker is the preferred way to perform asynchronous// operations.private:
void backgroundWorker1_RunWorkerCompleted(
Object^ sender,
RunWorkerCompletedEventArgs^ e)
{
this->textBox1->Text =
"This text was set safely by BackgroundWorker.";
}
#pragma region Windows Form Designer generated code
private:
void InitializeComponent()
{
this->textBox1 = gcnew System::Windows::Forms::TextBox();
this->setTextUnsafeBtn = gcnew System::Windows::Forms::Button();
this->setTextSafeBtn = gcnew System::Windows::Forms::Button();
this->setTextBackgroundWorkerBtn =
gcnew System::Windows::Forms::Button();
this->backgroundWorker1 =
gcnew System::ComponentModel::BackgroundWorker();
this->SuspendLayout();
//// textBox1//this->textBox1->Location = System::Drawing::Point(12, 12);
this->textBox1->Name = "textBox1";
this->textBox1->Size = System::Drawing::Size(240, 20);
this->textBox1->TabIndex = 0;
//// setTextUnsafeBtn//this->setTextUnsafeBtn->Location = System::Drawing::Point(15, 55);
this->setTextUnsafeBtn->Name = "setTextUnsafeBtn";
this->setTextUnsafeBtn->TabIndex = 1;
this->setTextUnsafeBtn->Text = "Unsafe Call";
this->setTextUnsafeBtn->Click +=
gcnew System::EventHandler(
this,&Form1::setTextUnsafeBtn_Click);
//// setTextSafeBtn//this->setTextSafeBtn->Location = System::Drawing::Point(96, 55);
this->setTextSafeBtn->Name = "setTextSafeBtn";
this->setTextSafeBtn->TabIndex = 2;
this->setTextSafeBtn->Text = "Safe Call";
this->setTextSafeBtn->Click +=
gcnew System::EventHandler(this,&Form1::setTextSafeBtn_Click);
//// setTextBackgroundWorkerBtn//this->setTextBackgroundWorkerBtn->Location =
System::Drawing::Point(177, 55);
this->setTextBackgroundWorkerBtn->Name =
"setTextBackgroundWorkerBtn";
this->setTextBackgroundWorkerBtn->TabIndex = 3;
this->setTextBackgroundWorkerBtn->Text = "Safe BW Call";
this->setTextBackgroundWorkerBtn->Click +=
gcnew System::EventHandler(
this,&Form1::setTextBackgroundWorkerBtn_Click);
//// backgroundWorker1//this->backgroundWorker1->RunWorkerCompleted +=
gcnew System::ComponentModel::RunWorkerCompletedEventHandler(
this,&Form1::backgroundWorker1_RunWorkerCompleted);
//// Form1//this->ClientSize = System::Drawing::Size(268, 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();
}
#pragma endregion
};
}
[STAThread]
int main()
{
Application::EnableVisualStyles();
Application::EnableRTLMirroring();
Application::Run(gcnew CrossThreadDemo::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.
Robust Programming
Caution:
When you use multithreading of any sort, your code can be exposed to very serious and complex bugs. For more information, see Managed Threading Best Practices before you implement any solution that uses multithreading.
当然,你也可以忽略InvalidOperationException,在非调试的状态下,该异常并不会被抛出,CLR-Debugger监测对Handle的可能存在的不一致地存取,而期望达到更稳健(robust)的代码,这也就是Cross-thread operation not valid后的真正动机。
private void AddListBoxItem(object item) { if (this.listBox1.InvokeRequired) { // This is a worker thread so delegate the task. this.listBox1.Invoke(new AddListBoxItemDelegate(this.AddListBoxItem), item); } else { // This is the UI thread so perform the task. this.listBox1.Items.Add(item); } }
Now you can simply call the AddListBoxItem method from anywhere and it will handle the delegation itself if required