HOW TO:進行對 Windows Form 控制項的安全執行緒呼叫
如果您使用多執行緒處理來改善 Windows Form 應用程式的效能,您必須確定以安全執行緒的方式呼叫控制項。
Windows Form 控制項的存取並非原本就採用安全執行緒的方式。 如果您有兩個或多個執行緒管理控制項的狀態,就有可能強制控制項進入不一致的狀態。 其他與執行緒有關的 Bug 也有可能如此,例如競爭情形和死結。 確定存取控制項是以安全執行緒的方式來執行是相當重要的工作。
從建立控制項之執行緒以外的其他執行緒呼叫該控制項,而且不使用 Invoke 方法,並不是安全的作法。 以下顯示不具備執行緒安全的呼叫範例。
' This event handler creates a thread that calls a
' Windows Forms control in an unsafe way.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' an unsafe call on the TextBox control.
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub
// 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 =
new Thread(new ThreadStart(this.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 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.";
}
.NET Framework 有助於偵測當您使用非安全執行緒的方式存取控制項的情況。 當您在偵錯工具中執行應用程式,且有建立控制項之執行緒以外的執行緒嘗試呼叫該控制項時,偵錯工具會引發 InvalidOperationException 和訊息:「存取控制項 control name 時所使用的執行緒與建立控制項的執行緒不同」。
在偵錯期間這個例外狀況會經常發生,且在某些情況下會發生於執行階段。 您可能會在針對使用 .NET Framework 2.0 版之前版本的 .NET Framework 所撰寫的應用程式進行偵錯時,發現這個例外狀況。 極力建議您在看到這個問題時一定要加以修正,但是您也可以透過將 CheckForIllegalCrossThreadCalls 屬性設定為 false 來將它停用。 這會讓您的控制項以像在 Visual Studio .NET 2003 和 .NET Framework 1.1 下一樣的方式執行。
注意事項 |
---|
如果您是在表單上使用 ActiveX 控制項,在偵錯工具下執行時可能會收到跨執行緒的 InvalidOperationException。 如果發生這種情況,就表示 ActiveX 控制項不支援多執行緒。 如需在 Windows Form 中使用 ActiveX 控制項的詳細資訊,請參閱 Windows Form 和 Unmanaged 應用程式。 如果您使用的是 Visual Studio,則只要停用 Visual Studio 裝載處理序就可以避免這種例外狀況。 如需詳細資訊,請參閱HOW TO:停用裝載處理序 和 HOW TO:停用裝載處理序 和 HOW TO:停用裝載處理序 和 HOW TO:停用裝載處理序. |
進行對 Windows Form 控制項的安全執行緒呼叫
若要對 Windows Form 控制項進行安全執行緒呼叫
查詢控制項的 InvokeRequired 屬性。
如果 InvokeRequired 傳回 true,就使用對控制項進行實際呼叫的委派 (Delegate) 呼叫 Invoke。
如果 InvokeRequired 傳回 false,便直接呼叫控制項。
在下列程式碼範例中,安全執行緒呼叫是在背景執行緒所執行的 ThreadProcSafe 方法中實作。 如果 TextBox 控制項的 InvokeRequired 傳回 true,ThreadProcSafe 方法會建立 SetTextCallback 的執行個體,並將該執行個體傳遞至表單的 Invoke 方法。 這會造成在建立 TextBox 控制項的執行緒上呼叫 SetText 方法,並且在這個執行緒內容中直接設定 Text 屬性。
' 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
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
// 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 =
new Thread(new ThreadStart(this.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 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
' SetTextCallback 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 Sub SetText(ByVal [text] As String)
' 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 Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
// 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
// SetTextCallback 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)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
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;
}
}
使用 BackgroundWorker 進行安全執行緒呼叫
在應用程式中實作多執行緒處理的慣用方式是使用 BackgroundWorker 元件。 BackgroundWorker 元件會使用多執行緒處理的事件驅動 (Event-Driven) 模型。 背景執行緒會執行您的 DoWork 事件處理常式,而建立控制項的執行緒則會執行 ProgressChanged 和 RunWorkerCompleted 事件處理常式。 您可以從 ProgressChanged 和 RunWorkerCompleted 事件處理常式呼叫控制項。
若要使用 BackgroundWorker 進行安全執行緒呼叫
建立方法,以執行您要在背景執行緒中完成的工作。 請不要在這個方法中呼叫由主執行緒建立的控制項。
建立方法,以便在背景工作完成之後回報工作結果。 您可以在這個方法中呼叫由主執行緒建立的控制項。
將在步驟 1 建立的方法繫結到 BackgroundWorker 執行個體的 DoWork 事件,並將在步驟 2 建立的方法繫結到同一個執行個體的 RunWorkerCompleted 事件。
若要啟動背景執行緒,請呼叫 BackgroundWorker 執行個體的 RunWorkerAsync 方法。
在下列程式碼範例中,DoWork 事件處理常式會使用 Sleep 來模擬需要一些時間的工作。 它不會呼叫表單的 TextBox 控制項。 TextBox 控制項的 Text 屬性是直接設定於 RunWorkerCompleted 事件處理常式。
' This BackgroundWorker is used to demonstrate the
' preferred way of performing asynchronous operations.
Private WithEvents backgroundWorker1 As BackgroundWorker
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private:
BackgroundWorker^ backgroundWorker1;
' 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 Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
' 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 Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub
// 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.";
}
// 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.";
}
您也可以使用 ProgressChanged 事件來回報背景工作的進度。 如需包含該事件的範例,請參閱 BackgroundWorker。
範例
下列程式碼範例是由內含三個按鈕和一個文字方塊的表單所組成的完整 Windows Form 應用程式。 第一個按鈕示範不安全的跨執行緒存取,第二個按鈕示範使用 Invoke 進行安全存取,而第三個按鈕則示範使用 BackgroundWorker 進行安全存取。
注意事項 |
---|
如需如何執行此範例的指示,請參閱 HOW TO:使用 Visual Studio 編譯及執行完整的 Windows Form 程式碼範例。 這個範例需要參考 System.Drawing 和 System.Windows.Forms 組件。 |
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([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(disposing As Boolean)
If disposing AndAlso (components IsNot Nothing) Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
' This event handler creates a thread that calls a
' Windows Forms control in an unsafe way.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' an unsafe call on the TextBox control.
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
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
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
' 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
' SetTextCallback 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 Sub SetText(ByVal [text] As String)
' 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 Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
' 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 Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
' 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 Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
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(240, 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(268, 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 thread that calls a
// Windows Forms control in an unsafe way.
private void setTextUnsafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.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 =
new Thread(new ThreadStart(this.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
// SetTextCallback 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)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new 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.";
}
#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(240, 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);
//
// backgroundWorker1
//
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
// Form1
//
this.ClientSize = new 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();
}
#endregion
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
}
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
using namespace System;
using namespace System::ComponentModel;
using namespace System::Threading;
using namespace 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::Run(gcnew CrossThreadDemo::Form1());
}
當您執行應用程式並按一下 [Unsafe Call] 按鈕時,文字方塊中會立即顯示 "Written by the main thread"。 兩秒鐘後,在嘗試進行不安全的呼叫時,Visual Studio 偵錯工具會指出發生例外狀況。 偵錯工具會停在背景執行緒嘗試直接寫入至文字方塊的那一行。 您必須重新啟動應用程式,才能測試另外兩個按鈕。 當您按一下 [Safe Call] 按鈕時,文字方塊中會出現 "Written by the main thread"。 兩秒鐘後,文字方塊會設定為 "Written by the background thread (Invoke)",表示已呼叫 Invoke 方法。 當您按一下 [Safe BW Call] 按鈕時,文字方塊中會出現 "Written by the main thread"。 兩秒鐘後,文字方塊會設定為 "Written by the main thread after the background thread completed",表示已呼叫 BackgroundWorker 之 RunWorkerCompleted 事件的處理常式。
穩固程式設計
警告
在使用任何類型的多執行緒處理時,您的程式碼可能會接觸到極嚴重且複雜的錯誤。 如需詳細資訊,請在實作使用多執行緒處理的任何方案之前參閱 Managed 執行緒處理的最佳實施方針。