https://www.youtube.com/watch?v=oCtTgYak2Rw
【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)
今天無意中看到有關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的線程結束後,才執行它的內容。
不過有兩種情況下,它會馬上執行:
/// <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
今天無意中看到有關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的線程結束後,才執行它的內容。
不過有兩種情況下,它會馬上執行:
/// <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