C#中Invoke的用法


在用.NET Framework框架的WinForm構建GUI程序界面時,如果要在控件的事件響應函數中改變控件的狀態,例如:某個按鈕上的文本原先叫“打開”,單擊之后按鈕上的文本顯示“關閉”,初學者往往會想當然地這么寫:

void ButtonOnClick(object sender,EventArgs e)

{

    button.Text="關閉";

}

這樣的寫法運行程序之后,可能會觸發異常,異常信息大致是“不能從不是創建該控件的線程調用它”。注意這里是“可能”,並不一定會觸發該種異常。造成這種異常的原因在於,控件是在主線程中創建的(比如this.Controls.Add(...);),進入控件的事件響應函數時,是在控件所在的線程,並不是主線程。在控件的事件響應函數中改變控件的狀態,可能與主線程發生線程沖突。如果主線程正在重繪控件外觀,此時在別的線程改變控件外觀,就會造成畫面混亂。不過這樣的情況並不總會發生,如果主線程此時在重繪別的控件,就可能逃過一劫,這樣的寫法可以正常通過,沒有觸發異常。

正確的寫法是在控件響應函數中調用控件的Invoke方法(其實如果大家以前用過C++ Builder的話,也會找到類似Invoke那樣的激活到主線程的函數)。Invoke方法會順着控件樹向上搜索,直到找到創建控件的那個線程(通常是主線程),然后進入那個線程改變控件的外觀,確保不發生線程沖突。正確寫法的示例如下:

void ButtonOnClick(object sender,EventArgs e)

{

    button.Invoke(new EventHandler(delegate

    {

        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的。

 

當一個控件的InvokeRequired屬性值為真時,說明有一個創建它以外的線程想訪問它,此時它將會在內部調用new MethodInvoker(LoadGlobalImage)來完成下面的步驟,這個做法保證了控件的安全,你可以這樣理解,有人想找你借錢,他可以直接在你的錢包中拿,這樣太不安全,因此必須讓別人先要告訴你,你再從自己的錢包把錢拿出來借給別人,這樣就安全了

 

 

another:

在設計中為了讓界面與邏輯分離,我的做法是使用事件,界面只要響應事件來處理界面的顯示就行了。而事件在邏輯處理中可能由不同的線程引發,這些事件的響應方法在修改界面中的控件內容時便會引發一個異常。

這時就用到了Control.InvokeRequired 屬性 與Invoke方法。

MSDN中說:
獲取一個值,該值指示調用方在對控件進行方法調用時是否必須調用 Invoke 方法,因為調用方位於創建控件所在的線程以外的線程中。 
如果控件的 Handle 是在與調用線程不同的線程上創建的(說明您必須通過 Invoke 方法對控件進行調用),則為 true;否則為 false。
Windows 窗體中的控件被綁定到特定的線程,不具備線程安全性 。因此,如果從另一個線程調用控件的方法,那么必須使用控件的一個 Invoke 方法來將調用封送到適當的線程。該屬性可用於確定是否必須調用 Invoke 方法,當不知道什么線程擁有控件時這很有用。

下面來說下這個的用法(我的一般做法):
首先定義一個委托,與這個事件處理函數的簽名一樣委托,當然直接使用該事件的委托也是可以的,如:

 private delegate void InvokeCallback( string msg);

然后就是判斷這個屬性的值來決定是否要調用Invoke函數:

 void m_comm_MessageEvent( string msg)
  {
 if (txtMessage.InvokeRequired)
 {
 InvokeCallbackmsgCallback = new InvokeCallback(m_comm_MessageEvent);
 txtMessage.Invoke(msgCallback, new object [] { msg } );
 } 
 else 
 {
 txtMessage.Text = msg;
 } 
 }

說明:這個函數就是事件處理函數,txtMessage是一個文本框。
這樣就做到了窗體中控件的線程安全性。

 

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

InvokeRequired 當前線程不是創建控件的線程時為true
比如你可以自己開一個Thread,或使用Timer的事件來訪問窗體上的控件的時候,在線程中窗體的這個屬性就是True的。

簡單的說,如果有兩個線程,Thread A和Thread B,並且有一個Control c,是在Thread A里面new的。
那么在Thread A里面運行的任何方法調用c.InvokeRequired都會返回false。
相反,如果在Thread B里面運行的任何方法調用c.InvokeRequired都會返回true。
是否是UI線程與結果無關。(通常Control所在的線程是UI線程,但是可以有例外)

也可以認為,在new Control()的時候,control用一個變量記錄下了當前線程,在調用InvokeRequired時,返回當前線程是否不等於new的時候記錄下來的那個線程。

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

我理解:如果InvokeRequired==true表示其它線程需要訪問控件,那么調用invoke來轉給控件owner處理。

 

 

異步中使用Invoke

private void button2_Click(object sender, EventArgs e)
{
//1、APM 異步編程模型,Asynchronous Programming Model
//C#1[基於IAsyncResult接口實現BeginXXX和EndXXX的方法] 
Debug.WriteLine("【Debug】主線程ID:" + Thread.CurrentThread.ManagedThreadId);

var request = WebRequest.Create("https://github.com/");
request.BeginGetResponse(new AsyncCallback(t =>//執行完成后的回調
{
var response = request.EndGetResponse(t);
var stream = response.GetResponseStream();//獲取返回數據流

using (StreamReader reader = new StreamReader(stream))
{
StringBuilder sb = new StringBuilder();
while (!reader.EndOfStream)
{
var content = reader.ReadLine();
sb.Append(content);
}
Debug.WriteLine("【Debug】" + sb.ToString().Trim().Substring(0, 100) + "...");//只取返回內容的前100個字符 
Debug.WriteLine("【Debug】異步線程ID:" + Thread.CurrentThread.ManagedThreadId);
label1.Invoke((Action)(() => { label1.Text = "執行完畢!"; }));//這里跨線程訪問UI需要做處理
}
}), null);

Debug.WriteLine("【Debug】主線程ID:" + Thread.CurrentThread.ManagedThreadId); 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM