轉自:http://blog.3snews.net/html/30/34530-27563.html
在多線程編程中,我們經常要在背景工作執行緒中去更新介面顯示,而在多線程中直接調用介面控制項的方法是錯誤的做法,Invoke 和 BeginInvoke 就是為瞭解決這個問題而出現的,使你在多線程中安全的更新介面顯示。
正確的做法是將背景工作執行緒中涉及更新介面的代碼封裝為一個方法,通過 Invoke 或者 BeginInvoke
去調用,兩者的區別就是一個導致背景工作執行緒等待,而另外一個則不會。
Framework架構的WinForm構建GUI程式介面時,如果要在控制項的事件響應函數中改變控制項的狀態
,例如:某個按鈕上的文本原先叫“開啟”,單擊之後按鈕上的文本顯示“關閉”,初學者往往會想當然地這麼寫:
void ButtonOnClick(object sender,EventArgs e)
{
button.Text="關閉";
}
這樣的寫法運行程式之後,可能會觸發異常,異常資訊大致是“不能從不是建立該控制項的線程調用它”。注意這裡是“可能”,並不一定會觸發該種異常。造 成這種異常的原因在於,控制項是在主線程中建立的(比如this.Controls.Add(...);),進入控制項的事件響應函數時,是在控制項所在的線 程,並不是主線程。在控制項的事件響應函數中改變控制項的狀態,可能與主線程發生線程衝突。如果主線程正在重繪控制面板,此時在別的線程改變控制面板,就會造 成畫面混亂。不過這樣的情況並不總會發生,如果主線程此時在重繪別的控制項,就可能逃過一劫,這樣的寫法可以正常通過,沒有觸發異常。
正確的寫法是在控制項響應函數中調用控制項的Invoke方法(其實如果大家以前用過C++
Builder的話,也會找到類似Invoke那樣的啟用到主線程的函數)。Invoke方法會順著控制項樹向上搜尋,直到找到建立控制項的那個線程(通常是主線程),然後進入那個線程改變控制項的外觀,確保不發生線程衝突。
而所謂的“一面響應操作,一面添加節點”永遠只能是相對的,使 UI 線程的負擔不至於太大而已,因為介面的正確更新始終要通過 UI
線程去做,我們要做的事情是在背景工作執行緒中包攬大部分的運算,而將對純粹的介面更新放到 UI 線程中去做,這樣也就達到了減輕 UI
線程負擔的目的了。
舉個簡單例子說明下使用方法,比如你在啟動一個線程,線上程的方法中想更新表單中的一個TextBox..
using System.Threading;
//啟動一個線程
Thread thread=new Thread(new
ThreadStart(DoWork));
thread.Start();
//線程方法
private void DoWork()
{
this.TextBox1.Text="我是一個文字框";
}
如果你像上面操作,在VS2005或2008裡是會有異常的...
正確的做法是用Invoke\BeginInvoke
using System.Threading;
namespace test
{
public partial class Form1 : Form
{
public delegate void MyInvoke(string str1,string str2);
public Form1()
{
InitializeComponent();
}
public void DoWork()
{
MyInvoke mi = new MyInvoke(UpdateForm);
this.BeginInvoke(mi, new Object[] {"我是文字框","haha"});
}
public void UpdateForm(string param1,string parm2)
{
this.textBox1.Text = param1+parm2;
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
}
}
}
注意代理的使用!