C#学习之多线程开发技术(十二)

矫情吗;* 2022-08-09 02:27 108阅读 0赞

【分析】浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang)

  今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间的区别。所以花了点时间研究了下。

  据msdn中介绍,它们最大的区别就是BeginInvoke属于异步执行的。

  • Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
  • Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。

msdn说明:







控件上的大多数方法只能从创建控件的线程调用。 如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从任何线程上安全调用的方法,它们是:InvokeBeginInvokeEndInvoke 和 CreateGraphics 在后台线程上创建控件的句柄之前调用 CreateGraphics 可能会导致非法的跨线程调用。 对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。 调用方法始终在控件的线程上调用自己的回调。

  

  于是用下面的代码进行初步的测试:  

  1.主线程调用Invoke   

复制代码

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

复制代码

输出:    

  2012052416564444.jpg

  从输出结果上可以看出,Invoke被调用后,是马上执行的。这点很好理解。

  2.主线程调用BeginInvoke

复制代码

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

复制代码

输出:  

  2012052417122736.jpg

  从输出能看出,只有当调用BeginInvoke的线程结束后,才执行它的内容。

  不过有两种情况下,它会马上执行:

  使用EndInvoke,检索由传递的 IAsyncResult 表示的异步操作的返回值。

复制代码

  1. /// <summary>
  2. /// 调用BeginInvoke、EndInvoke
  3. /// </summary>
  4. private void TestBeginInvokeEndInvoke()
  5. {
  6. listBox1.Items.Add("--begin--");
  7. var bi = listBox1.BeginInvoke(new Action(() =>
  8. {
  9. Thread.Sleep(1000);
  10. listBox1.Items.Add("BeginInvokeEndInvoke");
  11. }));
  12. listBox1.EndInvoke(bi);
  13. listBox1.Items.Add("--end--");
  14. }

复制代码

输出:  

  2012052417210535.jpg

  

  同一个控件调用Invoke时,会马上执行先前的BeginInvoke

复制代码

  1. /// <summary>
  2. /// 调用BeginInvoke、Invoke
  3. /// </summary>
  4. private void TestBeginInvokeInvoke()
  5. {
  6. listBox1.Items.Add("--begin--");
  7. listBox1.BeginInvoke(new Action(() =>
  8. {
  9. Thread.Sleep(1000);
  10. listBox1.Items.Add("BeginInvoke");
  11. }));
  12. listBox1.Invoke(new Action(() =>
  13. {
  14. listBox1.Items.Add("Invoke");
  15. }));
  16. listBox1.Items.Add("--end--");
  17. }

复制代码

输出:

  2012052417324410.jpg

  注:在主线程中直接调用Invoke、BeginInvoke、EndInvoke都会造成阻塞。所以应该利用副线程(支线线程)调用。

  3.支线线程调用Invoke

  创建一个线程,并在线程中调用Invoke,同时测试程序是在哪个线程中调用Invoke。

复制代码

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

复制代码

输出:  

  2012052517073261.jpg

  • Invoke的委托在主线程中执行
  • Invoke在支线程中调用也会阻塞主线程。(当点击button1后,我试图去点击button2,却发现程序被阻塞了)
  • Invoke还会阻塞支线程。(因为输出结果中没有出现After)  

  接着来测试下在支线程中调用BeginInvoke.

  4.支线线程调用BeginInvoke

复制代码

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

复制代码

输出:    

  2012052517243758.jpg

  • BeginInvoke在主线程中执行。
  • BeginInvoke在支线程中调用也会阻塞主线程。
  • BeginInvoke相对于支线程是异步的。

总结:**  **

  以下为了方便理解,假设如下:

    主线程表示Control.Invoke或Control.BeginInvoke中Control所在的线程,即创建该创建的线程。(一般为UI线程)

    支线程表示不同于主线程的调用Invoke或BeginInvoke的线程。

  • Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行。(也就是说如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在主线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死)
  • Invoke会阻塞主支线程,BeginInvoke只会阻塞主线程,不会阻塞支线程!因此BeginInvoke的异步执行是指相对于支线程异步,而不是相对于主线程异步。(从最后一个例子就能看出,程序运行点击button1)

发表评论

表情:
评论列表 (有 0 条评论,108人围观)

还没有评论,来说两句吧...

相关阅读