这些天给我们公司一个七八年的软件进行功能扩展,最大的感觉就是数据量大时界面特卡。打包安装后,运行起来就一直关不掉,得运行完毕才能关,运行时就是用任务管理器关都关不掉。于是想起了一个问题:Winform如何解决界面假死。
一直以来我只知道可以直接用Application.DoEvent()实现。其他的也就想想,估计多线程可以实现,但一直也没去研究。我构思的效果:现在界面在干一件事(正在进行中),忽然想起来要干另一件事(同时运行)。这种效果当前在winform上是不行的,界面会卡住且不接受任何操作。为实现这个效果,准备写个例子实现两个按钮分别批量更新两个文本框中的内容,并且更新的值能够及时反馈到UI上,最先应用Application.DoEvent()实现。主要代码如下:
private void btnUpdate_Click(object sender, EventArgs e) { btnUpdate.Enabled = false; for (int i = 30000; i > 0; i--) { tbValue.Text = i.ToString(); Application.DoEvents(); } btnUpdate.Enabled = true; }
private void btnOther_Click(object sender, EventArgs e) { btnOther.Enabled = false; for (int i = 30000; i > 0; i--) { tbOther.Text = i.ToString(); Application.DoEvents(); } btnOther.Enabled = true; }
代码说明:btnUpdate按钮可以更新tbValue文本框的值,btnOther按钮可以更新tbOther文本框的值。
结果发现会卡住其中一个,等另一个完成之后才继续完成,不是我想要的效果。于是乎只能运用多线程(仅限我知道的,其他应该有方法实现),找了找说用invoke方法。
private void btnUpdate_Click(object sender, EventArgs e) { // ThreadStart委托,它表示此线程开始执行时要调用的 方法。详细了解参照MSDN Thread thread = new Thread(new ThreadStart(this.ThreadUpdateValue1)); thread.Start(); } private void ThreadUpdateValue1() { btnUpdate.Enabled = false; for (int i = 30000; i > 0; i--) { this.Invoke(new Action<int>(this.UpdatetbValue1), i); } btnUpdate.Enabled = true; }
private void UpdatetbValue1(int value) { tbValue.Text = value.ToString(); } private void btnOther_Click(object sender, EventArgs e) { Thread thread = new Thread(new ThreadStart(this.ThreadUpdateValue2)); thread.Start(); } private void ThreadUpdateValue2() { btnOther.Enabled = false; for (int i = 30000; i > 0; i--) { this.Invoke(new Action<int>(this.UpdatetbValue2), i); } btnOther.Enabled = true; } private void UpdatetbValue2(int value) { tbOther.Text = value.ToString(); }
注:关于Action可以参考msdn http://msdn.microsoft.com/zh-cn/library/018hxwa8.aspx
发现报出错误:
还好百度无所不在,搜到这篇博客。只要设置Control.CheckForIllegalCrossThreadCalls = false;就可以了。测试发现还不如Application.DoEvents();的效果,直接卡掉直至全部完成才恢复,好歹用Application.DoEvents()还能看到是先后运行的。分析原因,窗体类被锁住了,Application.DoEvents()在计算和更新界面之间能够自动调度,而线程不能够自动调度。想起几天前看的一博客上说[MethodImpl(MethodImplOptions.Synchronized)]加在方法上时锁定对象,加在静态方法上时只锁定方法。于是乎将this.invoke改成tbValue.Invoke(new Action<int>(this.UpdatetbValue1), i);这样就只锁住该控件而不是锁住全部界面,测试一下效果发现,还不错,基本上能够感觉到同时在运行(只是视觉上哦)。至此以为大功告成,想到我现在的软件在运行时关不掉,那么这个在运行时关闭会怎样呢?一测,有收获,报错了:
错误提示说的很明确,另一个线程中去操作这个被关掉的窗口了。怎么处理呢?在线程中判断IsDisposed发现也确实为true,但就是不能中断,而是报错。在测试时还发现在点击事件中用try catch是捕捉不到新线程上的错误的。暂且在执行代码区域加上Try Catch可以解决,再找原因,看到百度空间上一博客,发现用Environment.Exit(0);才可以彻底退出。为了不添加复杂度,项目中计时的精确度只到秒,大概看到效果就行。
后记:写出来才有些意想不到的收获,比如异常部分,以前我对异常的认识一直认为只要在高层捕获异常就可以了,在单线程中可以,但今天才发现多线程中不可以(以前也没想过这个问题,对多线程基本上还只是处于认识阶段,写过例子,没用过)。有些东西平时不多去想一想,试一试,认识就不会提高。当然了,本人认识还处于初级阶段,还望路过的大牛们不吝指教。
项目源代码(还有项目中提到的一个异常的例子)。