189 8069 5689

C#中WinForm控件的跨线程更新,如何使用Invoke

目的:

10余年的滨湖网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销的优势是能够根据用户设备显示端的尺寸不同,自动调整滨湖建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“滨湖网站设计”,“滨湖网站推广”以来,每个客户项目都认真落实执行。

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

示例:

C#中WinForm控件的跨线程更新,如何使用Invoke

    

实现:

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

        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();
            }
        }

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

本文题目:C#中WinForm控件的跨线程更新,如何使用Invoke
URL地址:http://cdxtjz.com/article/jspdjp.html

其他资讯