C#中的Invoke ?轉(zhuǎn)載▼
在用.NET Framework框架的WinForm構(gòu)建GUI程序界面時(shí),如果要在控件的事件響應(yīng)函數(shù)中改變控件的狀態(tài),例如:某個(gè)按鈕上的文本原先叫“打開(kāi)”,單擊之后按鈕上的文本顯示“關(guān)閉”,初學(xué)者往往會(huì)想當(dāng)然地這么寫(xiě):
void ButtonOnClick(object sender,EventArgs e)
{
????button.Text="關(guān)閉";
}
這樣的寫(xiě)法運(yùn)行程序之后,可能會(huì)觸發(fā)異常,異常信息大致是“不能從不是創(chuàng)建該控件的線(xiàn)程調(diào)用它”。注意這里是“可能”,并不一定會(huì)觸發(fā)該種異常。造成這種異常的原因在于,控件是在主線(xiàn)程中創(chuàng)建的(比如this.Controls.Add(...);),進(jìn)入控件的事件響應(yīng)函數(shù)時(shí),是在控件所在的線(xiàn)程,并不是主線(xiàn)程。在控件的事件響應(yīng)函數(shù)中改變控件的狀態(tài),可能與主線(xiàn)程發(fā)生線(xiàn)程沖突。如果主線(xiàn)程正在重繪控件外觀,此時(shí)在別的線(xiàn)程改變控件外觀,就會(huì)造成畫(huà)面混亂。不過(guò)這樣的情況并不總會(huì)發(fā)生,如果主線(xiàn)程此時(shí)在重繪別的控件,就可能逃過(guò)一劫,這樣的寫(xiě)法可以正常通過(guò),沒(méi)有觸發(fā)異常。
正確的寫(xiě)法是在控件響應(yīng)函數(shù)中調(diào)用控件的Invoke方法(其實(shí)如果大家以前用過(guò)C++ Builder的話(huà),也會(huì)找到類(lèi)似Invoke那樣的激活到主線(xiàn)程的函數(shù))。Invoke方法會(huì)順著控件樹(shù)向上搜索,直到找到創(chuàng)建控件的那個(gè)線(xiàn)程(通常是主線(xiàn)程),然后進(jìn)入那個(gè)線(xiàn)程改變控件的外觀,確保不發(fā)生線(xiàn)程沖突。正確寫(xiě)法的示例如下:
void ButtonOnClick(object sender,EventArgs e)
{
????button.Invoke(new EventHandler(delegate
????{
????????button.Text="關(guān)閉";
????}));
}
Invoke方法需要?jiǎng)?chuàng)建一個(gè)委托。你可以事先寫(xiě)好函數(shù)和與之對(duì)應(yīng)的委托。不過(guò),若想直觀地在Invoke方法調(diào)用的時(shí)候就看到具體的函數(shù),而不是到別處搜尋的話(huà),上面的示例代碼是不錯(cuò)的選擇。
這樣的寫(xiě)法有一個(gè)煩人的地方:對(duì)不同的控件寫(xiě)法不同。對(duì)于TextBox,要TextBoxObject.Invoke,對(duì)于Label,又要LabelObject.Invoke。有沒(méi)有統(tǒng)一一點(diǎn)的寫(xiě)法呢?
主窗口類(lèi)本身也有Invoke方法。如果你不想對(duì)不同的控件寫(xiě)法不一樣,可以全部用this.Invoke:
void ButtonOnClick(object sender,EventArgs e)
{
????this.Invoke(new EventHandler(delegate
????{
????????button.Text="關(guān)閉";
????}));
}
在C# 3.0及以后的版本中有了Lamda表達(dá)式,像上面這種匿名委托有了更簡(jiǎn)潔的寫(xiě)法。.NET Framework?3.5及以后版本更能用Action封裝方法。例如以下寫(xiě)法可以看上去非常簡(jiǎn)潔:
void ButtonOnClick(object sender,EventArgs e)
{
????this.Invoke(new Action(()=>
????{
????????button.Text="關(guān)閉";
????}));
}
以上寫(xiě)法往往充斥著WinForm構(gòu)建的程序。
在微軟新一代的界面開(kāi)發(fā)技術(shù)WPF中,由于界面呈現(xiàn)和業(yè)務(wù)邏輯原生態(tài)地分開(kāi)在兩個(gè)線(xiàn)程中,所以控件的事件響應(yīng)函數(shù)就不必Invoke了。但是,如果手動(dòng)開(kāi)辟一個(gè)新線(xiàn)程,那么在這個(gè)新線(xiàn)程中改變控件的外觀,則還是要Invoke的。