在用.NET Framework框架的WinForm構建GUI程序界面時,若是要在控件的事件響應函數中改變控件的狀態,例如:某個按鈕上的文本原先叫「打開」,單擊以後按鈕上的文本顯示「關閉」,初學者每每會想固然地這麼寫:框架
void ButtonOnClick(object sender,EventArgs e)函數
{ui
button.Text="關閉";this
}線程
這樣的寫法運行程序以後,可能會觸發異常,異常信息大體是「不能從不是建立該控件的線程調用它」。注意這裏是「可能」,並不必定會觸發該種異常。形成這種異常的緣由在於,控件是在主線程中建立的(好比this.Controls.Add(...);),進入控件的事件響應函數時,是在控件所在的線程,並非主線程。在控件的事件響應函數中改變控件的狀態,可能與主線程發生線程衝突。若是主線程正在重繪控件外觀,此時在別的線程改變控件外觀,就會形成畫面混亂。不過這樣的狀況並不總會發生,若是主線程此時在重繪別的控件,就可能逃過一劫,這樣的寫法能夠正常經過,沒有觸發異常。orm
正確的寫法是在控件響應函數中調用控件的Invoke方法(其實若是你們之前用過C++ Builder的話,也會找到相似Invoke那樣的激活到主線程的函數)。Invoke方法會順着控件樹向上搜索,直到找到建立控件的那個線程(一般是主線程),而後進入那個線程改變控件的外觀,確保不發生線程衝突。正確寫法的示例以下:事件
void ButtonOnClick(object sender,EventArgs e)開發
{io
button.Invoke(new EventHandler(delegateobject
{
button.Text="關閉";
}));
}
Invoke方法須要建立一個委託。你能夠事先寫好函數和與之對應的委託。不過,若想直觀地在Invoke方法調用的時候就看到具體的函數,而不是到別處搜尋的話,上面的示例代碼是不錯的選擇。
這樣的寫法有一個煩人的地方:對不一樣的控件寫法不一樣。對於TextBox,要TextBoxObject.Invoke,對於Label,又要LabelObject.Invoke。有沒有統一一點的寫法呢?
主窗口類自己也有Invoke方法。若是你不想對不一樣的控件寫法不同,能夠所有用this.Invoke:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new EventHandler(delegate
{
button.Text="關閉";
}));
}
在C# 3.0及之後的版本中有了Lamda表達式,像上面這種匿名委託有了更簡潔的寫法。.NET Framework 3.5及之後版本更能用Action封裝方法。例如如下寫法能夠看上去很是簡潔:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="關閉";
}));
}
以上寫法每每充斥着WinForm構建的程序。
在微軟新一代的界面開發技術WPF中,因爲界面呈現和業務邏輯原生態地分開在兩個線程中,因此控件的事件響應函數就沒必要Invoke了。可是,若是手動開闢一個新線程,那麼在這個新線程中改變控件的外觀,則仍是要Invoke的。