2019年2月28日 星期四

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

https://www.cnblogs.com/wangshenhe/archive/2012/05/25/2516842.html

https://www.youtube.com/watch?v=oCtTgYak2Rw

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

  今天無意中看到有關Invoke和BeginInvoke的一些資料,不太清楚它們之間的區別。所以花了點時間研究了下。
  據msdn中介紹,它們最大的區別就是BeginInvoke屬於異步執行的。
  • Control. Invoke方法(Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委託。
  • Control.BeginInvoke方法( Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委託。
 msdn說明:
控件上的大多數方法只能從創建控件的線程調用。 如果已經創建控件的句柄,則除了InvokeRequired屬性以外,控件上還有四個可以從任何線程上安全調用的方法,它們是:InvokeBeginInvokeEndInvokeCreateGraphics 在後台線程上創建控件的句柄之前調用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( newAction(() =>
 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

沒有留言:

張貼留言