目的:

用WinForm(C#)搭建一个用户界面,一个进度条和一个按钮,按钮启动进度条,进度完成时停止更新

示例:


实现:

在按钮事件中设置循环,更新进度条

privatevoidbtnProgress_Click(objectsender,EventArgse){for(intii=0;ii<100;ii++){progressBar1.Value=ii+1;Thread.Sleep(100);}}


问题1:

进度条的更新良好,但更新过程中窗口停滞,因为按钮事件在窗口线程之中,执行在循环体中不会响应任何其他消息


解决1:

循环体中加入Application.DoEvents()

privatevoidbtnProgress_Click(objectsender,EventArgse){for(intii=0;ii<100;ii++){Application.DoEvents();progressBar1.Value=ii+1;Thread.Sleep(100);}}


问题2:

这种方法可以使窗口事件共享线程时间资源,但各个执行片断仍然是顺序执行的,占时较长的执行片断对其他事件影响显著。

Thread.Sleep(100)可以假设成一段复杂逻辑或计算,如果将Sleep(100)改成Sleep(1000)或更大,用户界面的效果便对问题1没什么明显改进。


解决2:

于是引入另外的线程,实现独自对进度进行控制,实际上是对占时逻辑或计算的线程分离,使之不占用用户界面的执行(时间)资源

publicvoidthreadProgress(){while(progressBar1.Value<100){progressBar1.Value++;Thread.Sleep(100);}}privatevoidbtnProgress_Click(objectsender,EventArgse){Threadthprog=newThread(threadProgress);thprog.Start();}

这段代码可以编译,但无法执行,执行时会遇到InvalidOperationException,原因是WinForm控件的状态更新只能在用户界面线程内进行,实际上是创建窗体的线程,(和响应clicked事件的相同)。此时可以跟踪Exception帮助文档给出的解决方案,How to: Make Thread-Safe Calls to Windows Forms Controls描述完整的问题定义和解决办法。

WinForm控件的跨线程更新要使用空间本身或线程内的Invoke方法,Invoke的参数要使用delegate函数指派。线程的行为需要修改。

publicdelegatevoidSetProgress(intpvv);voidGuiSetProgressMustInInvokeOrUIThread(intpvv){progressBar1.Value=pvv;}voidDeleSetProgress(intpvv){if(progressBar1.InvokeRequired){SetProgressspself=newSetProgress(GuiSetProgressMustInInvokeOrUIThread);progressBar1.Invoke(spself,newobject[]{pvv});}}publicvoidthreadProgress(){while(progressBar1.Value<100){DeleSetProgress(progressBar1.Value+1);Thread.Sleep(1000);}}

以上代码是文档中解决方案的拆解方法,DeleSetProgress确定为会被外部线程调用,它需要将实际的更新通过Invoke函数放回界面线程,这里使用delegate变量执行执行GuiSetProgressMustInInvokeOrUIThread。

帮助文档中对Invoke的解释是

Executes a delegate on the thread that owns the control's underlying window handle

从而保证在界面线程中的执行,当然执行片断是GuiSetProgressMustInInvokeOrUIThread中的部分,将复杂逻辑与界面更新实现分离


问题3:

以上代码会良好工作,但在更新过程中关闭窗口时,会遇到System.ObjectDisposedException。原因是窗口及空间被释放后,用户线程仍然在试图更新进度条。


解决3:

几个方案可供选择,窗口关闭前强制结束用户线程,窗口关闭前等待用户逻辑结束,或更复杂的线程管理逻辑。

这里等待线程结束,线程则需要在成员中保留,用户逻辑结束后设置标志,从而保证线程间调用的安全性。

privateThreadthProgress=null;publicvoidthreadProgress(){...thProgress=null;}privatevoidFormProgress_FormClosing(objectsender,FormClosingEventArgse){while(thProgress!=null){Application.DoEvents();Thread.Sleep(0);}}privatevoidbtnProgress_Click(objectsender,EventArgse){if(thProgress==null){thProgress=newThread(threadProgress);thProgress.Start();}}



附件:http://down.51cto.com/data/2367115