前言
最近在看前辈代码的时候,发现代码的有些地方穿插着这么一句:
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(); 作用不大;不使用委托时,能稍微避免一点线程冲突。