1. 程式人生 > >C#中Invoke 和 BeginInvoke的涵義和區別

C#中Invoke 和 BeginInvoke的涵義和區別

參考以下程式碼:

  1. public delegate void treeinvoke();
  2. privatevoidUpdateTreeView()
  3. {
  4.          MessageBox.Show(System.Threading.Thread.CurrentThread.Name);
  5. }
  6. privatevoid button1_Click(object sender,System.EventArgs e)
  7. {
  8.           System.Threading.Thread.CurrentThread.Name=“UIThread”;
  9.           treeView1.BeginInvoke
    (new treeinvoke(UpdateTreeView));
  10. }

看看執行結果,彈出的對話方塊中顯示的是 UIThread,這說明 BeginInvoke 所呼叫的委託根本就是在 UI 執行緒中執行的。

既然是在 UI 執行緒中執行,又何來“非同步執行”一說呢?

我們再看看下面的程式碼:

  1. public delegate void treeinvoke();
  2. privatevoidUpdateTreeView()
  3. {
  4. MessageBox.Show(Thread.CurrentThread.Name);
  5. }
  6. privatevoid button1_Click(object sender
    ,System.EventArgs e)
  7. {
  8. Thread.CurrentThread.Name=“UIThread”;
  9.        Thread th =newThread(newThreadStart(StartThread));
  10.        th.Start();
  11. }
  12. privatevoidStartThread()
  13. {
  14.           Thread.CurrentThread.Name=“Work Thread”;
  15.           treeView1.BeginInvoke(new treeinvoke(UpdateTreeView));
  16. }

再看看執行結果,彈出的對話方塊中顯示的還是 UIThread,這說明什麼?這說明 BeginInvoke 方法所呼叫的委託無論如何都是在 UI 執行緒中執行的。

那 BeginInvoke 究竟有什麼用呢?

在多執行緒程式設計中,我們經常要在工作執行緒中去更新介面顯示,而在多執行緒中直接呼叫介面控制元件的方法是錯誤的做法,具體的原因可以在看完我的這篇之後看看這篇:在多執行緒中如何呼叫Winform,如果你是大牛的話就不要看我這篇了,直接看那篇吧,反正那篇文章我沒怎麼看懂。

Invoke 和 BeginInvoke 就是為了解決這個問題而出現的,使你在多執行緒中安全的更新介面顯示。

正確的做法是將工作執行緒中涉及更新介面的程式碼封裝為一個方法,通過 Invoke 或者 BeginInvoke 去呼叫,兩者的區別就是一個導致工作執行緒等待,而另外一個則不會。

而所謂的“一面響應操作,一面新增節點”永遠只能是相對的,使 UI 執行緒的負擔不至於太大而以,因為介面的正確更新始終要通過 UI 執行緒去做,我們要做的事情是在工作執行緒中包攬大部分的運算,而將對純粹的介面更新放到 UI 執行緒中去做,這樣也就達到了減輕 UI 執行緒負擔的目的了。

而在那段更新樹節點的程式碼中,其實唯一起作用的程式碼是:System.Threading.Thread.Sleep(100);,它使 UI 執行緒有了處理介面訊息的機會,其實 數碼幽靈 將問題複雜化了,只要以下的程式碼就可以很好的工作了。

  1. privatevoid button1_Click_(object sender,System.EventArgs e)
  2. {
  3.           TreeNode tn;
  4.           for(int i=0;i<100000;i++)
  5.           {
  6.                    tn=newTreeNode(i.ToString());         
  7.                   this.treeView1.Nodes[0].Nodes.Add(tn);
  8. if(i%100==0)Application.DoEvents();
  9.           }
  10. }

近日,被Control的Invoke和BeginInvoke搞的頭大,就查了些相關的資料,整理如下。感謝這篇文章對我的理解Invoke和BeginInvoke的真正含義 。
(一)Control的Invoke和BeginInvoke
我們要基於以下認識:
(1)Control的Invoke和BeginInvoke與Delegate的Invoke和BeginInvoke是不同的。
(2)Control的Invoke和BeginInvoke的引數為delegate,委託的方法是在Control的執行緒上執行的,也就是我們平時所說的UI執行緒。

我們以程式碼(一)來看(Control的Invoke)

  1. private delegate voidInvokeDelegate();
  2. privatevoidInvokeMethod(){
  3. //C程式碼段
  4. }
  5. privatevoid butInvoke_Click(object sender,EventArgs e){
  6. //A程式碼段…….
  7. this.Invoke(newInvokeDelegate(InvokeMethod));
  8. //B程式碼段……
  9. }

你覺得程式碼的執行順序是什麼呢?記好Control的Invoke和BeginInvoke都執行在主執行緒即UI執行緒上
A——>C—————->B
解釋:(1)A在UI執行緒上執行完後,開始Invoke,Invoke是同步
(2)程式碼段B並不執行,而是立即在UI執行緒上執行InvokeMethod方法,即程式碼段C。
(3)InvokeMethod方法執行完後,程式碼段C才在UI執行緒上繼續執行。

看看程式碼(二),Control的BeginInvoke

  1. private delegate voidBeginInvokeDelegate();
  2. privatevoidBeginInvokeMethod(){
  3. //C程式碼段
  4. }
  5. privatevoid butBeginInvoke_Click(object sender,EventArgs e){
  6. //A程式碼段…….
  7. this.BeginInvoke(newBeginInvokeDelegate(BeginInvokeMethod));
  8. //B程式碼段……
  9. }

你覺得程式碼的執行順序是什麼呢?記好Control的Invoke和BeginInvoke都執行在主執行緒即UI執行緒上
A———–>B—————>C慎重,這個只做參考。。。。。,我也不肯定執行順序,如果有哪位達人知道的話請告知。
解釋::(1)A在UI執行緒上執行完後,開始BeginInvoke,BeginInvoke是非同步
(2)InvokeMethod方法,即程式碼段C不會執行,而是立即在UI執行緒上執行程式碼段B。
(3)程式碼段B執行完後(就是說butBeginInvoke_Click方法執行完後),InvokeMethod方法,即程式碼段C才在UI執行緒上繼續執行。

由此,我們知道:
Control的Invoke和BeginInvoke的委託方法是在主執行緒,即UI執行緒上執行的。也就是說如果你的委託方法用來取花費時間長的資料,然後更新介面什麼的,千萬別在UI執行緒上呼叫Control.Invoke和Control.BeginInvoke,因為這些是依然阻塞UI執行緒的,造成介面的假死。

那麼,這個非同步到底是什麼意思呢?

非同步是指相對於呼叫BeginInvoke的執行緒非同步,而不是相對於UI執行緒非同步,你在UI執行緒上呼叫BeginInvoke ,當然不行了。----摘自”Invoke和BeginInvoke的真正涵義”一文中的評論。
BeginInvoke的原理是將呼叫的方法Marshal成訊息,然後呼叫Win32 API中的RegisterWindowMessage()向UI視窗傳送訊息。----摘自”Invoke和BeginInvoke的真正涵義”一文中的評論。

(二)我們用Thread來呼叫BeginInvoke和Invoke
我們開一個執行緒,讓執行緒執行一些耗費時間的操作,然後再用Control.Invoke和Control.BeginInvoke回到使用者UI執行緒,執行介面更新。

程式碼(三)  Thread呼叫Control的Invoke

  1. privateThread invokeThread;
  2. private delegate void invokeDelegate();
  3. privatevoidStartMethod(){
  4. //C程式碼段……
  5. Control.Invoke(new invokeDelegate(invokeMethod));
  6. //D程式碼段……
  7. }
  8. privatevoid invokeMethod(){
  9. //E程式碼段
  10. }
  11. privatevoid butInvoke_Click(object sender,EventArgs e){
  12. //A程式碼段…….
  13. invokeThread =newThread(newThreadStart(StartMethod));
  14. invokeThread.Start();
  15. //B程式碼段……
  16. }

你覺得程式碼的執行順序是什麼呢?記好Control的Invoke和BeginInvoke都執行在主執行緒即UI執行緒上
A——>(Start一開始B和StartMethod的C就同時執行)—->(C執行完了,不管B有沒有執行完,invokeThread把訊息封送(invoke)給UI執行緒,然後自己等待)—->UI執行緒處理完butInvoke_Click訊息後,處理invokeThread封送過來的訊息,執行invokeMethod方法,即程式碼段E,處理往後UI執行緒切換到invokeThread執行緒。
這個Control.Invoke是相對於invokeThread執行緒同步的,阻止了其執行。

解釋:
1。UI執行A
2。UI開執行緒InvokeThread,B和C同時執行,B執行線上程UI上,C執行線上程invokeThread上。
3。invokeThread封送訊息給UI,然後自己等待,UI處理完訊息後,處理invokeThread封送的訊息,即程式碼段E
4。UI執行完E後,轉到執行緒invokeThread上,invokeThread執行緒執行程式碼段D

程式碼(四)  Thread呼叫Control的BeginInvoke

  1. privateThread beginInvokeThread;
  2. private delegate void beginInvokeDelegate();
  3. privatevoidStartMethod(){
  4. //C程式碼段……
  5. Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod));
  6. //D程式碼段……
  7. }
  8. privatevoid beginInvokeMethod(){
  9. //E程式碼段
  10. }
  11. privatevoid butBeginInvoke_Click(object sender,EventArgs e){
  12. //A程式碼段…….
  13. beginInvokeThread =newThread(newThreadStart(StartMethod));
  14. beginInvokeThread .Start();
  15. //B程式碼段……
  16. }

你覺得程式碼的執行順序是什麼呢?記好Control的Invoke和BeginInvoke都執行在主執行緒即UI執行緒上
A在UI執行緒上執行—–>beginInvokeThread執行緒開始執行,UI繼續執行程式碼段B,併發地invokeThread執行程式碼段C————–>不管UI有沒有執行完程式碼段B,這時beginInvokeThread執行緒把訊息封送給UI,單自己並不等待,繼續向下執行——–>UI處理完butBeginInvoke_Click訊息後,處理beginInvokeThread執行緒封送過來的訊息。


解釋:
1。UI執行A
2。UI開執行緒beginInvokeThread,B和C同時執行,B執行線上程UI上,C執行線上程beginInvokeThread上。
3。beginInvokeThread封送訊息給UI,然後自己繼續執行程式碼D,UI處理完訊息後,處理invokeThread封送的訊息,即程式碼段E
有點疑問:如果UI先執行完畢,是不是有可能過了段時間beginInvokeThread才把訊息封送給UI,然後UI才繼續執行封送的訊息E。如圖淺綠的部分。

Control的BeginInvoke是相對於呼叫它的執行緒,即beginInvokeThread相對是非同步的。
因此,我們可以想到。如果要非同步取耗費長時間的資料,比如從資料庫中讀大量資料,我們應該這麼做。
(1)如果你想阻止呼叫執行緒,那麼呼叫程式碼(三),程式碼段D刪掉,C改為耗費長時間的操作,因為這個操作是在另外一個執行緒中做的。程式碼段E改為更新介面的方法。
(2)如果你不想阻止呼叫執行緒,那麼呼叫程式碼(四),程式碼段D刪掉,C改為耗費長時間的操作,因為這個操作是在另外一個執行緒中做的。程式碼段E改為更新介面的方法。

相關推薦

C#Invoke BeginInvoke涵義區別

參考以下程式碼: public delegate void treeinvoke(); privatevoidUpdateTreeView() {          MessageBox.Show(System.Threading.Thread.CurrentThread.Name); } privat

C++關於[]靜態數組new分配的動態數組的區別分析

zid dad ima lin aer uem asa iba ash %E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3java%E8%99%9A%E6%8B%9F%E6%9C%BA7---%E7%BA%BF%E7%A8%8B%E5%AE%89%E5

C#數組、ArrayListList三者的區別

collect comm 兩個 根據 -s lis 數據打包 功能 target 在C#中數組,ArrayList,List都能夠存儲一組對象,那麽這三者到底有什麽樣的區別呢。 數組 數組在C#中最早出現的。在內存中是連續存儲的,所以它的索引速度非常快,而且賦值

C#重寫(override)覆蓋(new)的區別

實現 div del end 抽象方法 ring reg strong AR 重寫 用關鍵字 virtual 修飾的方法,叫虛方法。可以在子類中用override 聲明同名的方法,這叫“重寫”。相應的沒有用virtual修飾的方法,我們叫它實方法。重寫會改變父類方法的功能。

C#執行緒靜態變數普通靜態變數的區別

當欄位被ThreadStatic特性修飾後,它的值在每個執行緒中都是不同的,即每個執行緒對static欄位都會重新分配記憶體空間,就當然於一次new操作,這樣一來,由於static欄位所產生的問題也就沒有了,這種static資料上下文是可以被接受的

C++堆(heap)棧(stack)的區別(面試被問到的題目)

說起會了解這個東西,還是比較尷尬的,在學校裡面老師一般不會講解C++的堆和棧,大多數人瞭解的堆和棧是資料結構裡面的概念,而這裡一般面試官想問的是C++的記憶體分割槽管理方式。 首先說明,在C++中,記憶體分為5個區:堆、佔、自由儲存區、全域性/靜態儲存區、常量儲存區 棧:

C++換行符‘\n’控制符‘endl’的區別(coutprintf區別

1.顯示字串時,在字串中包含換行符,而不是在末尾加上endl,可以減少輸入量 2.如果生成一個空行,則兩種方法的輸入量相同,但對大多數人而言輸入endl更為方便 3.顯示引號括起來的字串通常使用換行符

C++陣列、連結串列vector等容器之間的區別

1. 各個容器之間區別 ① vector   (連續的空間儲存,可以使用[]操作符)快速的訪問隨機的元素,快速的在末尾插入元素,但是在序列中間歲間的插入,刪除元素要慢,而且如果一開始分配的空

淺析C++的初始化列表(區別賦值初始化)

派生類不能直接訪問基類的私有成員,而必須通過基類方法進行訪問。 具體來說,派生類建構函式必須使用基類建構函式。 建立派生類物件時,程式首先建立基類物件。C++使用初始化列表完成這項工作。 RatedPlayer::RatedPlayer(int r, co

C#的值類型引用類型以及堆棧

堆棧 ima tro nbsp 在線 hid class類 sed pen 引用類型如:string,Object,class等總是在從托管堆上分配的,C#中new操作符返回對象的內存地址--也就是指向對象數據的內存地址。 以下是值類型與引用類型的表: 我們來看下

C#怎樣實現序列化反序列化

image .com 合並 小白 str mat get new ons 我們想要將數據進行持久化的操作的話,也就是將數據寫入到文件中,我們在C#中可以通過IO流來操作,同時也可以通過序列化來操作,本人是比較推薦使用序列化操作的 因為我們如果想要將一個對象持久化到文件中

C#的 具名參數 可選參數

調用方法 4.0 frame framework 必須 遇到 錯誤 方法 public 具名參數 和 可選參數 是 C# framework 4.0 出來的新特性。 一. 常規方法定義及調用 public void Demo1(string x, int y) {

淺談C#的值類型引用類型

title log 創建 編譯 設計 編寫 通過 發布 構造 在C#中,值類型和引用類型是相當重要的兩個概念,必須在設計類型的時候就決定類型實例的行為。如果在編寫代碼時不能理解引用類型和值類型的區別,那麽將會給代碼帶來不必要的異常。很多人就是因為沒有弄清楚這兩個概念從而在編

【.Net】淺談C#的值類型引用類型

rem 理解 amp div net 親情 實例 函數 大小 在C#中,值類型和引用類型是相當重要的兩個概念,必須在設計類型的時候就決定類型實例的行為。如果在編寫代碼時不能理解引用類型和值類型的區別,那麽將會給代碼帶來不必要的異常。很多人就是因為沒有弄清楚這兩個概念從而在編

c#的引用類型值類型

有意義 tle 線程同步 pan trac 理解 也會 自己的 方法 一,c#中的值類型和引用類型 眾所周知在c#中有兩種基本類型,它們分別是值類型和引用類型;而每種類型都可以細分為如下類型: 什麽是值類型和引用類型 什麽是值類型: 進一步研究文

c#的裏氏轉換Java強制類型轉換在多態的應用

readline color extends pre pri console AS 定義 spa 在c#中: 註意: 子類並沒有繼承父類的構造函數,而是會默認調用父類那個無參數的構造函數。 如果一個子類繼承了一個父類,那麽這個子類除了可以使用自己的成員外,還可以使用從父類

C的除法,商余數的大小、符號如何確定

spa 匯編碼 來看 div 最大整數 () 以及 有時 取余 對於C中的除法,商和余數的大小、符號是如何確定的呢?在C89中,只規定了如果兩個數為正整數,那麽余數的符號為正,並且商的值是接近真實值的最大整數。比如5 / 2,那麽商就是2,余數就是1。但是,C89裏面對於被

C# 字符串string字節數組byte[]的轉換

arr nco bytes bytearray 數組 byte[] sys efault ring string轉byte[]: byte[] byteArray = System.Text.Encoding.Default.GetBytes ( str ); byte

C#如何利用操作符過載轉換操作符 (轉載)

操作符過載 有的程式語言允許一個型別定義操作符應該如何操作型別的例項,比如string型別和int型別都過載了(==)和(+)等操作符,當編譯器發現兩個int型別的例項使用+操作符的時候,編譯器會生成把兩個整數加到一起的程式碼。當編譯器發現兩個string型別的例項使用+操作符的時候,編譯器會生成把兩個

C/C++如何表示正無窮負無窮

我們可以使用系統提供的常量: 如果是int型,可以用INT_MAX表示正無窮,INT_MIN表示負無窮,需要包含標頭檔案limits.h; 如果是double型,可以用DBL_MAX表示正無窮,-DBL_MAX表示負無窮(注意不是DBL_MIN),需要包含標頭檔案float.h。 我