如何:對(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 編寫(xiě)的應(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)文章
C#數(shù)組中List, Dictionary的相互轉(zhuǎn)換問(wèn)題
這篇文章主要介紹了C#數(shù)組中List, Dictionary的相互轉(zhuǎn)換問(wèn)題,本文給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12
RSA密鑰--JAVA和C#的區(qū)別及聯(lián)系
這篇文章主要介紹了關(guān)于RSA密鑰事件JAVA和C#的區(qū)別及聯(lián)系,文章從RSA語(yǔ)法介紹開(kāi)始展開(kāi)詳細(xì)介紹了C#轉(zhuǎn)JAVA及JAVA轉(zhuǎn)C#,需要的小伙伴可以可以參考一下2021-10-10
c# openxml 刪除xlsx、xls的外鏈?zhǔn)纠a
要?jiǎng)h除一個(gè) Excel 文件(.xlsx)中的外部鏈接(external links),你可以使用 OpenXML SDK,本文演示如何使用 OpenXML SDK 刪除外部鏈接,感興趣的朋友一起看看吧2024-01-01
C#使用iTextSharp設(shè)置PDF所有頁(yè)面背景圖功能實(shí)例
這篇文章主要介紹了C#使用iTextSharp設(shè)置PDF所有頁(yè)面背景圖功能,實(shí)例分析了C#使用iTextSharp設(shè)置PDF頁(yè)面背景圖的實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓
這篇文章主要為大家詳細(xì)介紹了C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
C#實(shí)現(xiàn)自定義光標(biāo)并動(dòng)態(tài)切換
這篇文章主要為大家詳細(xì)介紹了如何利用C#語(yǔ)言實(shí)現(xiàn)自定義光標(biāo)、并動(dòng)態(tài)切換光標(biāo)類型,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-07-07
C#中csv文件與DataTable互相導(dǎo)入處理實(shí)例解析
這篇文章主要介紹了C#中csv文件與DataTable互相導(dǎo)入處理實(shí)例解析,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08
C#從實(shí)體對(duì)象集合中導(dǎo)出Excel的代碼
數(shù)據(jù)的導(dǎo)出是項(xiàng)目中經(jīng)常要實(shí)現(xiàn)的功能,就拿最常見(jiàn)的要導(dǎo)出成Excel來(lái)說(shuō),網(wǎng)上看來(lái)看去,都是介紹從Datatable中導(dǎo)出2008-08-08
C#實(shí)現(xiàn)保存文件時(shí)重名自動(dòng)生成新文件的方法
這篇文章主要介紹了C#實(shí)現(xiàn)保存文件時(shí)重名自動(dòng)生成新文件的方法,涉及C#針對(duì)保存文件時(shí)出現(xiàn)重命名情況的自動(dòng)處理技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07


注意
警告