DoEvents() 小实验

前言

最近在看前辈代码的时候,发现代码的有些地方穿插着这么一句:

Application.DoEvents();

心里有点纳闷,程序都执行到这里了,难道还没 Do 么,还是说在等待什么。

查了一些资料后发现这货就是为了响应界面不至于假死用的。

举个例子

按钮按下,label1 的 Text 属性从 1 开始增加到 39999

public partial class Form1 : Form
{
     public Form1()
     {
        InitializeComponent();
     }

     private void button1_Click(object sender, EventArgs e)
     {
          for (int i = 0; i < 40000; i++)
          {
              label1.Text = i.ToString();
              //Application.DoEvents(); 
          }
     }
}

Application.DoEvents(); 注销的时候效果如左图,  启用的时候效果如右图:

                        

图1                                                                                                    图2

可见没有 Application.DoEvents(); 的时候,出现了假死,而有 Application.DoEvents();  的时候,程序仍然能够响应界面操作。

但是,很显然不要 Application.DoEvents();  的时候这段循环代码更快的执行完毕了。右边不知道要加到什么时候才能到达 39999。

扩展1

很显然上面的 for 循环,无耻的写在了主线程中,如果我们开一个副线程去跑这个任务呢?

 public partial class Form1 : Form
 {
      public Form1()
      {
          InitializeComponent();
      }

      private void button1_Click(object sender, EventArgs e)
      {
          Update();
      }

      private new void Update()
      {
          Task.Factory.StartNew(() =>
          {
              for (int i = 0; i < 40000; i++) 
              { 
                  label1.Invoke(new Action(() =>
                  {
                     label1.Text = i.ToString();
                  }));
                  //Application.DoEvents();
              }
         });
     }
}

Application.DoEvents(); 注销的时候效果如左图,  启用的时候效果如右图:

                        

图3                                                                                                    图4

可以看到,在副线程中,两者效果一样,移动鼠标的时候会卡,而且再次点击按钮会卡死,可能是线程冲突。

毕竟已经委托给 UI 线程了,即使再 DoEvents(),人家也不理你了,但是仔细观察会发现,此时循环跑的比上面图 2 快一些。

扩展2

如果我们不用委托呢,直接跨线程操作会怎样?

 public partial class Form1 : Form
 {
      public Form1()
      {
          InitializeComponent();
          CheckForIllegalCrossThreadCalls = false;
      }

      private void button1_Click(object sender, EventArgs e)
      {
          Update();
      }

      private new void Update()
      {
          Task.Factory.StartNew(() =>
          {
              for (int i = 0; i < 40000; i++) 
              { 
                  label1.Text = i.ToString();
                  //Application.DoEvents();
              }
         });
     }
}

Application.DoEvents(); 注销的时候效果如左图,  启用的时候效果如右图:

                        

图5                                                                                                    图6

可以看出来,控件在刷新,两者速度差不多,也能保证响应界面的同时,但是跟前面的比较起来,速度最慢。

图5 在循环没有结束的时候,再次按下按钮,可以看出来是两个线程同时在刷新这个控件。但是在按下第 3 次的时候就卡死了。

图6 在循环没有结束的时候,再按 4 ~ 5 次,界面才卡死。

卡死的时候报错:

所以在副线程中加 Application.DoEvents();  还是有点作用的。

扩展3

Winform 中,可以开启窗体的 DoubleBuffered 属性,开启此属性后,再做上面的实验,发现。。。没啥变化,就不上图了。

这里也能理解,双缓冲是用来应对大量的、复杂的图元数据需要重绘时出现闪烁现象的。

综上

在主线程中,使用 Application.DoEvents(); ,可以起到即刷新界面又响应鼠标的作用。

在副线程中,当使用委托时, Application.DoEvents(); 作用不大;不使用委托时,能稍微避免一点线程冲突。

发表评论

Powered by WordPress | Theme Revised from Doo

苏ICP备18047621号

Copyright © 2017-2024 追光者博客