C#中WinForm控件的跨线程更新,如何使用Invoke
目的:
10余年的滨湖网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销的优势是能够根据用户设备显示端的尺寸不同,自动调整滨湖建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“滨湖网站设计”,“滨湖网站推广”以来,每个客户项目都认真落实执行。
用WinForm(C#)搭建一个用户界面,一个进度条和一个按钮,按钮启动进度条,进度完成时停止更新
示例:
实现:
在按钮事件中设置循环,更新进度条
private void btnProgress_Click(object sender, EventArgs e) { for (int ii = 0; ii < 100; ii++) { progressBar1.Value = ii + 1; Thread.Sleep(100); } }
问题1:
进度条的更新良好,但更新过程中窗口停滞,因为按钮事件在窗口线程之中,执行在循环体中不会响应任何其他消息
解决1:
循环体中加入Application.DoEvents()
private void btnProgress_Click(object sender, EventArgs e) { for (int ii = 0; ii < 100; ii++) { Application.DoEvents(); progressBar1.Value = ii + 1; Thread.Sleep(100); } }
问题2:
这种方法可以使窗口事件共享线程时间资源,但各个执行片断仍然是顺序执行的,占时较长的执行片断对其他事件影响显著。
Thread.Sleep(100)可以假设成一段复杂逻辑或计算,如果将Sleep(100)改成Sleep(1000)或更大,用户界面的效果便对问题1没什么明显改进。
解决2:
于是引入另外的线程,实现独自对进度进行控制,实际上是对占时逻辑或计算的线程分离,使之不占用用户界面的执行(时间)资源
public void threadProgress() { while (progressBar1.Value < 100) { progressBar1.Value++; Thread.Sleep(100); } } private void btnProgress_Click(object sender, EventArgs e) { Thread thprog = new Thread(threadProgress); thprog.Start(); }
这段代码可以编译,但无法执行,执行时会遇到InvalidOperationException,原因是WinForm控件的状态更新只能在用户界面线程内进行,实际上是创建窗体的线程,(和响应clicked事件的相同)。此时可以跟踪Exception帮助文档给出的解决方案,How to: Make Thread-Safe Calls to Windows Forms Controls描述完整的问题定义和解决办法。
WinForm控件的跨线程更新要使用空间本身或线程内的Invoke方法,Invoke的参数要使用delegate函数指派。线程的行为需要修改。
public delegate void SetProgress(int pvv); void GuiSetProgressMustInInvokeOrUIThread(int pvv) { progressBar1.Value = pvv; } void DeleSetProgress(int pvv) { if (progressBar1.InvokeRequired) { SetProgress spself = new SetProgress(GuiSetProgressMustInInvokeOrUIThread); progressBar1.Invoke(spself, new object[] { pvv }); } } public void threadProgress() { 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:
几个方案可供选择,窗口关闭前强制结束用户线程,窗口关闭前等待用户逻辑结束,或更复杂的线程管理逻辑。
这里等待线程结束,线程则需要在成员中保留,用户逻辑结束后设置标志,从而保证线程间调用的安全性。
private Thread thProgress = null; public void threadProgress() { ... thProgress = null; } private void FormProgress_FormClosing(object sender, FormClosingEventArgs e) { while (thProgress != null) { Application.DoEvents(); Thread.Sleep(0); } } private void btnProgress_Click(object sender, EventArgs e) { if (thProgress == null) { thProgress = new Thread(threadProgress); thProgress.Start(); } }
文章标题:C#中WinForm控件的跨线程更新,如何使用Invoke
网站网址:http://scjbc.cn/article/jspdjp.html