1. 程式人生 > >【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)

【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)

info start result 初步 總結 inter blank rap 傳遞

  今天無意中看到有關Invoke和BeginInvoke的一些資料,不太清楚它們之間的區別。所以花了點時間研究了下。

  據msdn中介紹,它們最大的區別就是BeginInvoke屬於異步執行的。

  • Control.Invoke 方法 (Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委托。
  • Control.BeginInvoke 方法 (Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委托。
msdn說明:

控件上的大多數方法只能從創建控件的線程調用。 如果已經創建控件的句柄,則除了 InvokeRequired 屬性以外,控件上還有四個可以從任何線程上安全調用的方法,它們是:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。

在後臺線程上創建控件的句柄之前調用 CreateGraphics 可能會導致非法的跨線程調用。 對於所有其他方法調用,則應使用調用 (invoke) 方法之一封送對控件的線程的調用。 調用方法始終在控件的線程上調用自己的回調。

  

  於是用下面的代碼進行初步的測試:  

  1.主線程調用Invoke   

技術分享圖片
 1         /// <summary>
 2         /// 直接調用Invoke
 3         /// </summary>
 4         private void TestInvoke()
 5         {
 6             listBox1.Items.Add("--begin--");
 7             listBox1.Invoke(new Action(() =>
 8             {
 9                 listBox1.Items.Add("Invoke");
10             }));
11 
12             Thread.Sleep(1000);
13             listBox1.Items.Add("--end--");
14         }
技術分享圖片

輸出:    

  技術分享圖片

  從輸出結果上可以看出,Invoke被調用後,是馬上執行的。這點很好理解。

  2.主線程調用BeginInvoke

技術分享圖片
 1         /// <summary>
 2         /// 直接調用BeginInvoke
 3         /// </summary>
 4         private void TestBeginInvoke()
 5         {
 6             listBox1.Items.Add("--begin--");
 7             var bi = listBox1.BeginInvoke(new Action(() =>
 8             {
 9                 //Thread.Sleep(10000);
10                 listBox1.Items.Add("BeginInvoke");
11             }));
12             Thread.Sleep(1000);
13             listBox1.Items.Add("--end--");
14         }
技術分享圖片

輸出:  

  技術分享圖片

  從輸出能看出,只有當調用BeginInvoke的線程結束後,才執行它的內容。

  不過有兩種情況下,它會馬上執行:

  使用EndInvoke,檢索由傳遞的 IAsyncResult 表示的異步操作的返回值。

技術分享圖片
        /// <summary>
        /// 調用BeginInvoke、EndInvoke
        /// </summary>
        private void TestBeginInvokeEndInvoke()
        {
            listBox1.Items.Add("--begin--");
            var bi = listBox1.BeginInvoke(new Action(() =>
            {
                Thread.Sleep(1000);
                listBox1.Items.Add("BeginInvokeEndInvoke");
            }));
            listBox1.EndInvoke(bi);
            listBox1.Items.Add("--end--");
        }
技術分享圖片

輸出:  

  技術分享圖片

  

  同一個控件調用Invoke時,會馬上執行先前的BeginInvoke

技術分享圖片
        /// <summary>
        /// 調用BeginInvoke、Invoke
        /// </summary>
        private void TestBeginInvokeInvoke()
        {
            listBox1.Items.Add("--begin--");
            listBox1.BeginInvoke(new Action(() =>
                {
                    Thread.Sleep(1000);
                    listBox1.Items.Add("BeginInvoke");
                }));
            listBox1.Invoke(new Action(() =>
                {
                    listBox1.Items.Add("Invoke");
                }));
            listBox1.Items.Add("--end--");
        }
技術分享圖片

輸出:

  技術分享圖片

  註:在主線程中直接調用Invoke、BeginInvoke、EndInvoke都會造成阻塞。所以應該利用副線程(支線線程)調用。

  3.支線線程調用Invoke

  創建一個線程,並在線程中調用Invoke,同時測試程序是在哪個線程中調用Invoke。

技術分享圖片
 1         /// <summary>
 2         /// 線程調用Invoke
 3         /// </summary>
 4         private void ThreadInvoke()
 5         {
 6             listBox1.Items.Add("--begin--");
 7             new Thread(() =>
 8             {
 9                 Thread.CurrentThread.Name = "ThreadInvoke";
10                 string temp = "Before!";
11                 listBox1.Invoke(new Action(() =>
12                     {
13                         Thread.Sleep(10000);
14                         this.listBox1.Items.Add(temp +="Invoke!" + Thread.CurrentThread.Name);
15                     }));
16                 temp += "After!";
17             }).Start();
18             listBox1.Items.Add("--end--");          
19         }
20 
21 
22         private void button1_Click(object sender, EventArgs e)
23         {
24             Thread.CurrentThread.Name = "Main";
25             ThreadInvoke();
26         }
27 
28         private void button2_Click(object sender, EventArgs e)
29         {
30             listBox1.Items.Add("button2_Click");
31         }
技術分享圖片

輸出:  

  技術分享圖片

  • Invoke的委托在主線程中執行
  • Invoke在支線程中調用也會阻塞主線程。(當點擊button1後,我試圖去點擊button2,卻發現程序被阻塞了)
  • Invoke還會阻塞支線程。(因為輸出結果中沒有出現After)  

  接著來測試下在支線程中調用BeginInvoke.

  4.支線線程調用BeginInvoke

技術分享圖片
 1         /// <summary>
 2         /// 線程調用BeginInvoke
 3         /// </summary>
 4         private void ThreadBeginInvoke()
 5         {
 6             listBox1.Items.Add("--begin--");
 7             new Thread(() =>
 8             {
 9                 Thread.CurrentThread.Name = "ThreadBeginInvoke";
10                 string temp = "Before!";
11                 listBox1.BeginInvoke(new Action(() =>
12                 {
13                     Thread.Sleep(10000);
14 this.listBox1.Items.Add(temp += "Invoke!" + Thread.CurrentThread.Name); 15 })); 17 temp += "After!"; 18 }).Start(); 19 listBox1.Items.Add("--end--"); 20 } 21 22 23 private void button1_Click(object sender, EventArgs e) 24 { 25 Thread.CurrentThread.Name = "Main"; 26 ThreadBeginInvoke(); 27 } 28 29 private void button2_Click(object sender, EventArgs e) 30 { 31 listBox1.Items.Add("button2_Click"); 32 }
技術分享圖片

輸出:    

  技術分享圖片

  • BeginInvoke在主線程中執行。
  • BeginInvoke在支線程中調用也會阻塞主線程。
  • BeginInvoke相對於支線程是異步的。

總結:  

  以下為了方便理解,假設如下:

    主線程表示Control.Invoke或Control.BeginInvoke中Control所在的線程,即創建該創建的線程。(一般為UI線程)

    支線程表示不同於主線程的調用Invoke或BeginInvoke的線程。

  • Control的Invoke和BeginInvoke的委托方法是在主線程,即UI線程上執行。(也就是說如果你的委托方法用來取花費時間長的數據,然後更新界面什麽的,千萬別在主線程上調用Control.Invoke和Control.BeginInvoke,因為這些是依然阻塞UI線程的,造成界面的假死)
  • Invoke會阻塞主支線程,BeginInvoke只會阻塞主線程,不會阻塞支線程!因此BeginInvoke的異步執行是指相對於支線程異步,而不是相對於主線程異步。(從最後一個例子就能看出,程序運行點擊button1)

                                       SamWang

                                      2012-05-25

作者:SamWang
出處:http://wangshenhe.cnblogs.com/
本文版權歸作者和博客園共有,歡迎圍觀轉載。轉載時請您務必在文章明顯位置給出原文鏈接,謝謝您的合作。

【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)