1. 程式人生 > >Winform控制元件多執行緒操作控制元件的解決方案

Winform控制元件多執行緒操作控制元件的解決方案

1)在要訪問的控制元件的那個窗體,定義公共屬於或公共方法,這樣可以實現間接的訪問

2)在主窗體,定義方法來包裝上面定義的另外一個窗體的方法,然後定義委託。

3)如果當執行緒,直接委託實現就可以,如果多執行緒,用invoke技術

在多執行緒程式設計中,我們經常要在工作執行緒中去更新介面顯示,而在多執行緒中直接呼叫介面控制元件的方法是錯誤的做法,Invoke 和 BeginInvoke 就是為了解決這個問題而出現的,使你在多執行緒中安全的更新介面顯示。

正確的做法是將工作執行緒中涉及更新介面的程式碼封裝為一個方法,通過 Invoke 或者 BeginInvoke 去呼叫,兩者的區別就是一個導致工作執行緒等待,而另外一個則不會。

而所謂的“一面響應操作,一面新增節點”永遠只能是相對的,使 UI 執行緒的負擔不至於太大而已,因為介面的正確更新始終要通過 UI 執行緒去做,我們要做的事情是在工作執行緒中包攬大部分的運算,而將對純粹的介面更新放到 UI 執行緒中去做,這樣也就達到了減輕 UI 執行緒負擔的目的了。

再舉個簡單例子說明下使用方法,比如你在啟動一個執行緒,線上程的方法中想更新窗體中的一個TextBox..

類似:

using System.Threading;

//啟動一個執行緒
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();

//執行緒方法
private void DoWork()
{
//其他操作
//比如將介面的TextBox內容設定一下
this.TextBox1.Text="我是一個文字框";
}

如果你像上面操作,在VS2005或2008裡是會有異常的...

正確的做法是用Invoke..

改為如下:
//定義一個委託
public delegate void MyInvoke(string str);
//更新介面的方法
private void UpdateTextBox(string str);
{
//更新
this.TextBox1.Text=str;
}

//啟動一個執行緒
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();

//執行緒方法
private void DoWork()
{
//其他操作
//比如將介面的TextBox內容設定一下
MyInvoke mi=new MyInvoke(UpdateTextBox);
this.BeginInvoke(mi,new object[]{"我是一個文字框"});
}  

----------------------------------------------------------------------

delegate void delegateRefreshStatus(string txt);
private void RefreshStatus(string txt)
{
try
{
if (this.InvokeRequired)
{
this.BeginInvoke(new delegateRefreshStatus(RefreshStatus), txt);
}
else
{
this.lblStatus.Text = txt;
}
}
catch (Exception ex)
{
MsgBox.ShowInfo("更新介面狀態時發生異常:" + ex.Message);
}
}
把需要修改的的控制元件也作為引數傳遞過來就可以解決都很多控制元件的問題了
中RefreshStatus(string txt,TextBox TxtBox)
{
....
TxtBox.Text=txt;
....
}

 ----------------------------------------------------------------------

先看這樣的一個例子:

點選"多執行緒訪問"按鈕標籤中文字"此標籤被另一個執行緒設定文字"會變為"Hello"!
程式碼是這樣寫的:
/// <summary>
/// 設定標籤的文字
/// </summary>
private void SetLableText()
{
      this.label1.Text = "Hello!";
}
/// <summary>

/// 設定標籤的按鈕事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
     System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.SetLableText));
     setLabelTextThread.Start();    
}

按照想法,這個功能是完成了,執行.點選按鈕,卻出現了異常:

分析:label標籤控制元件是主執行緒建立的,不能直接從另一個執行緒訪問.可以這樣認為:不能跨執行緒直接訪問控制元件;
如何才能實現這個功能呢?
在.NET中,所有的控制元件都是從System.Windows.Forms.Control類派生,Control類提供了一個Invoke()方法,用於在建立控制元件的執行緒中訪問執行緒.它的定義如下:
public Object Invoke(Delegate method);
它的引數為一個委託,代表建立控制元件的執行緒中要執行的方法.
可以利用這個方法來實現這個功能.
首先定義一個委託:
public delegate void setLabelTextDelegate();//定義一個setLabelTextDelegate()
的委託
在定義一個委託變數:
private setLabelTextDelegate setLabelText;
在窗體的建構函式中給這個委託變數初始化:
public Form1()
{
        InitializeComponent();
        this.setLabelText = this.SetLableText;//SetLableText為上面的"設定標籤的文字"的方法
}
然後在定義一個方法.方法裡使用Invoke
private void ThreadMethod()
{
        this.label1.Invoke(this.setLabelText);//setLabelText為上面定義的委託變數
}
接著把按鈕事件裡的程式碼修改一下:
/// <summary>
        /// 設定標籤的按鈕事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.SetLableText));//這個方法修改為ThreadMethod,即:
// System.Threading.Thread setLabelTextThread = new System.Threading.Thread(new System.Threading.ThreadStart(this.ThreadMethod));

            setLabelTextThread.Start();
        }
這個就OK了,執行.點選:

功能實現:

不過"設定標籤的文字"的方法SetLableText()是沒有引數的,在很多情況下,我們寫的方法都是需要引數的,下面我就把這個例子改成有引數的,並演示如何傳遞引數:
首先:改造"設定標籤的文字"的方法SetLableText()變成有引數的:
/// <summary>
/// 設定標籤的文字
/// </summary>
private void SetLableText(string info)
{
     this.label1.Text = info;
}
既然這個方法有引數了,與它對應的委託應該使用引數:
public delegate void setLabelTextDelegate(string infor);

定義的委託變數還是在建構函式中初始化,這個不用改變什麼:
public Form1()
{
        InitializeComponent();
        this.setLabelText = this.SetLableText;
}
既然使用了引數,那麼Invoke()這個方法應該會有過載的方法吧?
對Invoke()這個方法是有過載的,它的定義如下:
public Object Invoke(Delegate method,param Object [] args);
第二個引數是一個object的陣列,就意味著,可以把需要傳遞的引數放到這個數組裡面來進行傳遞
對ThreadMethod()改造:
private void ThreadMethod(Object info)
{
         this.label1.Invoke(this.setLabelText, new object[] { info});
}
注意紅色部分,為新增的引數
最後是按鈕事件的改造了:

/// <summary>
        /// 設定標籤的按鈕事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            System.Threading.Thread setLabelTextThread = new System.Threading.Thread(this.ThreadMethod);
            setLabelTextThread.Start("Hello!");
           
        }

改造成功:

跨執行緒訪問控制元件步驟可以總結一下:
(1)將訪問的控制元件程式碼封裝為一個方法;
(2)根據方法自定義一個對應委託;
(3)增加一個定義的委託型別的欄位,並把前面訪問控制元件的方法"掛接"到此欄位中;
(4)編寫一個執行緒方法,在此方法中呼叫要訪問控制元件的Invoke方法,並把定義好了的委託欄位做為引數傳入.
(5)在合適的地方建立執行緒並啟動執行
轉自:http://www.cnblogs.com/popo-vavamin/archive/2008/07/07/1237502.html