1.背景介紹
在Windows Form中使用多線程時,除了建立控制項的線程以外(就是主線程),絕對不要在任何其他線程裡面調用控制項的成員(只有極個別情況例外),也就是說控制項屬於建立它的線程,不能從其他線程裡面訪問。這一條適用於所有從System.Windows.Forms.Control派生的控制項(因此可以說是幾乎所有控制項),包括Form控制項本身也是。舉一反三,我們很容易得出這樣的結論,控制項的子控制項必須由建立控制項的線程來建立,比如一個表單上的按鈕,比如由建立表單的線程來建立,因此,一個視窗中的所有控制項實際上都活在同一個線程之中。在實際編程時,大多數的軟體的做法都是讓同一線程負責全部的控制項,這就是我們所說的UI線程(就是主線程)。
由於上述限制,我們可能會感到很不方便,的確,當我們利用一個新建立的線程來執行某些花時間的運算時,怎樣知道運算進度如何並通過UI反映給使用者呢?解決方案很多!比如熟悉多線程編程的使用者很快會想到,我們採用一些低級的同步方法,工作者線程把狀態儲存到一個同步對象中,讓UI線程輪詢(Polling)該對象並反饋給使用者就可以了。不過,這還是挺麻煩的,實際上不用這樣做,Control類(及其衍生類別)對象有一個Invoke方法很特別,這是少數幾個不受線程限制的成員之一。我們前面說到,絕對不要在任何其他線程裡面調用非本線程建立的控制項的成員時,也說了“只有極個別情況例外”,這個Invoke方法就是極個別情況之一----Invoke方法可以從任何線程裡面調用。
2.Invoke,BeginInvoke介紹
Invoke方法的參數很簡單,一個委託,一個參數表(可選),而Invoke方法的主要功能就是協助你在UI線程(即建立控制項的線程)上調用委託所指定的方法。Invoke方法首先檢查發出調用的線程(即當前線程)是不是UI線程,如果是,直接執行委託指向的方法,如果不是,它將切換到UI線程,然後執行委託指向的方法。不管當前線程是不是UI線程,Invoke都阻塞直到委託指向的方法執行完畢,然後切換回傳出調用的線程(如果需要的話),返回。注意,使用Invoke方法時,UI線程不能處於阻塞狀態。以下MSDN裡關於Invoke方法的說明:
“控制項上有四種方法可以安全地從任何線程進行調用:Invoke、BeginInvoke、EndInvoke 和CreateGraphics。對於所有其他方法調用,則應使用調用 (invoke) 方法之一封送對控制項的線程的調用。
委託可以是 EventHandler 的執行個體,在此情況下,發送方參數將包含此控制項,而事件參數將包含 EventArgs.Empty。委託還可以是 MethodInvoker 的執行個體或採用 void 參數列表的其他任何委託。調用 EventHandler 或 MethodInvoker 委託比調用其他類型的委託速度更快。”
說說BeginInvoke,毫無疑問這是Invoke的非同步版本(Invoke是同步完成的),BeginInvoke方法總是使用UI線程。相對Invoke而言,使用BeginInvoke稍稍麻煩一點,但還是那句話,非同步比同步效果好,儘管複雜些。比如同步方法可能出現這樣一種死結情況:工作者線程通過Invoke同步調用UI線程裡的方法時會阻塞,而萬一UI線程正在等待工作者線程做某件事時怎麼辦?因此,能夠使用非同步方法呼叫時應盡量使用非同步方法呼叫。
下面是一個具體的例子來說明Invoke和BeginInvoke的區別:
namespace ControlInvoke_BeginInvoke
{
public partial class Form1 : Form
{
private Thread myThread;
private delegate void DisplayMethod(string content);
private delegate void BeginInvokeDelegate();
string strContent = string.Empty;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
if (myThread != null && myThread.IsAlive)
{
this.btnStart.Text = "Stop";
myThread.Abort();
}
else
{
this.txtMain.Text = string.Empty;
string strContent = string.Empty;
this.btnStart.Text = "Start";
this.myThread = new Thread(new ThreadStart(GetContent));
this.myThread.Start();
}
}
private void GetContent()
{
this.Invoke(new DisplayMethod(Refresh_DisplayContent), "Hello World");
MessageBox.Show("BeginInvoke");
}
private void Refresh_DisplayContent(string content)
{
Thread.Sleep(5000);
lock (this.strContent)
{
this.strContent += content + "\r\n";
}
this.txtMain.Text = this.strContent;
this.txtMain.Refresh();
}
}
}
上面代碼執行結果將是畫面上先顯示"Hello World“,然後再彈出對話方塊。如果使用this.BeginInvoke
private void GetContent()
{
this.BeginInvoke(new DisplayMethod(Refresh_DisplayContent), "Hello World");
MessageBox.Show("BeginInvoke");
}
結果正好相反,由於BeginInvoke為非同步作業,即Refresh_DisplayContent方法的執行相對於myThread 線程是非同步。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/chenlu1982/archive/2008/02/18/2102692.aspx