小結“線程間操作無效: 從不是創建控件的線程訪問它” 錯誤的解決方法

在編程中經常會遇到在一個按鈕中執行復雜操作,並將複雜操作最後返回的值加入一個ListView或ComboBox中候選。這個時候程序會卡,當程序員將這些卡代碼放進線程(Thread)中後發現當對控件操作時出現“線程間操作無效: 從不是創建控件的線程訪問它”異常。
         爲什麼.net不讓我們跨線程操作控件,這是有好處的。因爲如果你的線程多了,那麼當兩個線程同時嘗試將一個控件變爲自己需要的狀態時,線程的死鎖就會發 生。但是難道就是因爲這個原因,我們就只能讓程序卡着麼?當然不是,這裏教大家一個解決方案:用BackGroundWorker
         這裏通過一個實例來告訴大家BackGroundWorker的用法。
         首先我們先定義一個BackGroundWorker,大家可以去面板上拖一個,也可以自己手工定義一個。

this .backgroundWorker_Combo = new System.ComponentModel.BackgroundWorker(); // 定義一個backGroundWorker
this .backgroundWorker_Combo.WorkerSupportsCancellation = true ; // 設置能否取消任務
   this .backgroundWorker_Combo.DoWork += new System.ComponentModel.DoWorkEventHandler( this .backgroundWorker_Combo_DoWork); // 讓backgroundWorker做的事
this .backgroundWorker_Combo.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler( this .backgroundWorker_Combo_RunWorkerCompleted); // 當backgroundWorker做完後發生的事件
         如果是從面板上拖的,那麼請在DoWork事件上雙擊,添加那些你想在背景線程中執行的代碼,也就是那些可能會讓你卡的代碼。
         然後再在RunWorkerCompleted事件上雙擊,添加那些你想往控件裏操作的代碼。
         這裏有一個開發實例,講的是實現類似Google搜索中下拉列表的實現。其思路是在DoWork中搜索數據庫,在Completed中將搜出來的東西放進去。
         本文需要一個backgroundWorker,一個ComboBox控件
         static char x;
        
/**/ /**/ /**/ /// <summary>
        
/// 接受從DLL搜出來的項目
        
/// </summary>

         private string [] global_ListItem;

        
private void backgroundWorker_Combo_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e)
        
{ // 如果數組中有東西,那麼加入ComboBox
             if (global_ListItem.Length > 0 )
            
{
                
this .comboBox_App.Items.Clear();
                
this .comboBox_App.Items.AddRange(global_ListItem);
             }

         }


        
private void backgroundWorker_Combo_DoWork( object sender, DoWorkEventArgs e)
        
{
             global_ListItem
= Form_Setting.Global_DBC.SimilarFilter(x); // 這是一個DLL中的方法,用於查找所有以X打頭的項目,並放入一個數組中
         }


        
private void comboBox_App_TextChanged( object sender, EventArgs e)
        
{ // 當用戶鍵入一個字母時去數據庫查
             ComboBox cb = sender as ComboBox;
            
if (cb.Text.Length == 1 )
            
{
                 x
= cb.Text[ 0 ];
                
this .backgroundWorker_Combo.RunWorkerAsync();
             }

         }

 

 

 

那麼是不是用Thread就不行呢?其實不是的,.net中也有線程安全的控件訪問。

 

訪問 Windows 窗體控件本質上不是線程安全的。如果有兩個或多個線程操作某一控件的狀態,則可能會迫使該控件進入一種不一致的狀態。還可能出現其他與線程相關的 bug,包括爭用情況和死鎖。確保以線程安全方式訪問控件非常重要。

.NET Framework 有助於在以非線程安全方式訪問控件時檢測到這一問題。在調試器中運行應用程序時,如果創建某控件的線程之外的其他線程試圖調用該控件,則調試器會引發一個 InvalidOperationException,並提示消息:“從不是創建控件 control name 的線程訪問它。”

此異常在調試期間和運行時的某些情況下可靠地發生。強烈建議您在顯示此錯誤信息時修復此問題。在調試以 .NET Framework 2.0 版之前的 .NET Framework 編寫的應用程序時,可能會出現此異常。
注意
可以通過將 CheckForIllegalCrossThreadCalls 屬性的值設置爲 false 來禁用此異常。這會使控件以與在 Visual Studio 2003 下相同的方式運行。

下面的代碼示例演示如何從輔助線程以線程安全方式和非線程安全方式調用 Windows 窗體控件。它演示一種以非線程安全方式設置 TextBox 控件的 Text 屬性的方法,還演示兩種以線程安全方式設置 Text 屬性的方法。

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadDemo
{
public class Form1 : Form
{
// 代理實現異步調用以設置TextBox控件text屬性
delegate void SetTextCallback(string text);
// 此線程用來演示線程安全和非安全兩種方式來調用一個windows窗體控件
private Thread demoThread = null;
// 此後臺工作者(BackgroundWorker)用來演示執行異步操作的首選方式
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);
}
// 此事件句柄創建一個ie線程以非安全方式調用一個windows窗體控件
private void setTextUnsafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
// 此方法在工作者線程執行並且對TextBox控件作非安全調用
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}
// 此事件句柄創建一個以線程安全方式調用windows窗體控件的線程
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// 此方法在工作者線程執行並且對TextBox控件作線程安全調用
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// 此方法演示一個對windows窗體控件作線程安全調用的模式
//
// 如果調用線程和創建TextBox控件的線程不同,這個方法創建
// 代理SetTextCallback並且自己通過Invoke方法異步調用它
// 如果相同則直接設置Text屬性
private void SetText(string text)
{
// InvokeRequired需要比較調用線程ID和創建線程ID
// 如果它們不相同則返回true
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
// 此事件句柄通過調用RunWorkerAsync開啓窗體的BackgroundWorker
//
// 當BackgroundWorker引發RunworkerCompleted事件的時候TextBox
// 控件的Text屬性被設置
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
// 此事件句柄設置TextBox控件的Text屬性,它在創建TextBox控件的線程
// 中被調用,所以它的調用是線程安全的
//
// BackgroundWorker是執行異步操作的首選方式
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());
}
}
}
發佈了33 篇原創文章 · 獲贊 4 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章