如何:對(duì)Windows 窗體控件進(jìn)行線程安全調(diào)用
示例
訪問(wèn) Windows 窗體控件本質(zhì)上不是線程安全的。如果有兩個(gè)或多個(gè)線程操作某一控件的狀態(tài),則可能會(huì)迫使該控件進(jìn)入一種不一致的狀態(tài)。還可能出現(xiàn)其他與線程相關(guān)的 bug,包括爭(zhēng)用情況和死鎖。確保以線程安全方式訪問(wèn)控件非常重要。
.NET Framework 有助于在以非線程安全方式訪問(wèn)控件時(shí)檢測(cè)到這一問(wèn)題。在調(diào)試器中運(yùn)行應(yīng)用程序時(shí),如果創(chuàng)建某控件的線程之外的其他線程試圖調(diào)用該控件,則調(diào)試器會(huì)引發(fā)一個(gè) InvalidOperationException,并提示消息:“從不是創(chuàng)建控件 control name 的線程訪問(wèn)它?!?
此異常在調(diào)試期間和運(yùn)行時(shí)的某些情況下可靠地發(fā)生。強(qiáng)烈建議您在顯示此錯(cuò)誤信息時(shí)修復(fù)此問(wèn)題。在調(diào)試以 .NET Framework 2.0 版之前的 .NET Framework 編寫的應(yīng)用程序時(shí),可能會(huì)出現(xiàn)此異常。
![]() |
---|
可以通過(guò)將 CheckForIllegalCrossThreadCalls 屬性的值設(shè)置為 false 來(lái)禁用此異常。這會(huì)使控件以與在 Visual Studio 2003 下相同的方式運(yùn)行。 |
下面的代碼示例演示如何從輔助線程以線程安全方式和非線程安全方式調(diào)用 Windows 窗體控件。它演示一種以非線程安全方式設(shè)置 TextBox 控件的 Text 屬性的方法,還演示兩種以線程安全方式設(shè)置 Text 屬性的方法。
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()); } } }
對(duì) Windows 窗體控件的非線程安全調(diào)用
對(duì) Windows 窗體控件的非線程安全調(diào)用方式是從輔助線程直接調(diào)用。調(diào)用應(yīng)用程序時(shí),調(diào)試器會(huì)引發(fā)一個(gè) InvalidOperationException,警告對(duì)控件的調(diào)用不是線程安全的。
// 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."; }
對(duì) Windows 窗體控件的線程安全調(diào)用
對(duì) Windows 窗體控件進(jìn)行線程安全調(diào)用
-
查詢控件的 InvokeRequired 屬性。
-
如果 InvokeRequired 返回 true,則使用實(shí)際調(diào)用控件的委托來(lái)調(diào)用 Invoke。
-
如果 InvokeRequired 返回 false,則直接調(diào)用控件。
在下面的代碼示例中,此邏輯是在一個(gè)稱為 SetText 的實(shí)用工具方法中實(shí)現(xiàn)的。名為 SetTextDelegate 的委托類型封裝 SetText 方法。TextBox 控件的 InvokeRequired 返回 true 時(shí),SetText 方法創(chuàng)建 SetTextDelegate 的一個(gè)實(shí)例,并調(diào)用窗體的 Invoke 方法。這使得 SetText 方法被創(chuàng)建 TextBox 控件的線程調(diào)用,而且在此線程上下文中將直接設(shè)置 Text 屬性。
// 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."); }

// 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;
}
}
使用 BackgroundWorker 進(jìn)行的線程安全調(diào)用
在應(yīng)用程序中實(shí)現(xiàn)多線程的首選方式是使用 BackgroundWorker 組件。BackgroundWorker 組件使用事件驅(qū)動(dòng)模型實(shí)現(xiàn)多線程。輔助線程運(yùn)行 DoWork 事件處理程序,創(chuàng)建控件的線程運(yùn)行 ProgressChanged 和 RunWorkerCompleted 事件處理程序。注意不要從 DoWork 事件處理程序調(diào)用您的任何控件。
下面的代碼示例不異步執(zhí)行任何工作,因此沒(méi)有 DoWork 事件處理程序的實(shí)現(xiàn)。TextBox 控件的 Text 屬性在 RunWorkerCompleted 事件處理程序中直接設(shè)置。
// 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."; }
Windows 窗體上的 ActiveX 控件
如果在窗體上使用 ActiveX 控件,則在調(diào)試器下運(yùn)行時(shí)可能會(huì)收到線程間 InvalidOperationException。發(fā)生這種情況時(shí),ActiveX 控件不支持多線程處理。有關(guān)使用 Windows 窗體的 ActiveX 控件的更多信息,請(qǐng)參見(jiàn) Windows 窗體和非托管應(yīng)用程序。
如果您使用的是 Visual Studio,則可以通過(guò)禁用 Visual Studio 宿主進(jìn)程來(lái)防止此異常發(fā)生。
可靠編程
![]() |
---|
使用任何一種多線程時(shí),代碼都容易產(chǎn)生非常嚴(yán)重而復(fù)雜的 bug。有關(guān)更多信息,請(qǐng)?jiān)趯?shí)現(xiàn)使用多線程的任何解決方案之前參見(jiàn)托管線程處理的最佳做法。 |
請(qǐng)參見(jiàn)
任務(wù)
如何:在后臺(tái)運(yùn)行操作如何:實(shí)現(xiàn)使用后臺(tái)操作的窗體
參考
BackgroundWorker其他資源
使用 .NET Framework 開(kāi)發(fā)自定義 Windows 窗體控件Windows 窗體和非托管應(yīng)用程序
- C#窗體編程(windows forms)禁止窗口最大化的方法
- .NET Web開(kāi)發(fā)之.NET MVC框架介紹
- asp.net 學(xué)習(xí)之路 項(xiàng)目整體框架簡(jiǎn)單的搭建
- 混合語(yǔ)言編程—C#使用原生的Directx和OpenGL繪圖的方法
- 深入c# GDI+簡(jiǎn)單繪圖的具體操作步驟(四)
- 深入c# GDI+簡(jiǎn)單繪圖的具體操作步驟(三)
- 深入c# GDI+簡(jiǎn)單繪圖的具體操作步驟(二)
- 深入c# GDI+簡(jiǎn)單繪圖的具體操作步驟(一)
- C#實(shí)現(xiàn)自定義定時(shí)組件的方法
- C#網(wǎng)絡(luò)編程基礎(chǔ)之進(jìn)程和線程詳解
- Windows窗體的.Net框架繪圖技術(shù)實(shí)現(xiàn)方法
相關(guān)文章
深入分析NTFS中文件被鎖定導(dǎo)致Process.Start失敗的詳解
本篇文章是對(duì)NTFS中文件被鎖定導(dǎo)致Process.Start失敗進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Unity3D網(wǎng)格功能生成球體網(wǎng)格模型
這篇文章主要為大家詳細(xì)介紹了Unity3D網(wǎng)格功能生成球體網(wǎng)格模型,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02C#正則表達(dá)式分解和轉(zhuǎn)換IP地址實(shí)例(C#正則表達(dá)式大全 c#正則表達(dá)式語(yǔ)法)
這是我發(fā)了不少時(shí)間整理的C#的正則表達(dá)式,新手朋友注意一定要手冊(cè)一下哦,這樣可以節(jié)省很多寫代碼的時(shí)間。下面進(jìn)行了簡(jiǎn)單總結(jié)2013-12-12利用Aspose.Cells和Excel模板導(dǎo)出統(tǒng)計(jì)數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了利用Aspose.Cells和Excel模板導(dǎo)出復(fù)雜的統(tǒng)計(jì)數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12C#中實(shí)現(xiàn)查找字符串中指定字符位置方法小結(jié)
這篇文章主要為大家介紹了C#中實(shí)現(xiàn)查找字符串中指定字符位置的常用方法,本文將以"."字符為例,詳細(xì)講解這些方法的具體使用,需要的可以參考下2024-02-02C# 使用SharpZipLib生成壓縮包的實(shí)例代碼
SharpZipLib是一個(gè)C#的類庫(kù),主要用來(lái)解壓縮Zip,GZip,BZip2,Tar等格式,是以托管程序集的方式實(shí)現(xiàn),可以方便的應(yīng)用于其他的項(xiàng)目之中。本文通過(guò)一個(gè)實(shí)例代碼給大家介紹了C# 使用SharpZipLib生成壓縮包的方法,感興趣的朋友跟隨小編一起看看吧2018-09-09