码迷,mamicode.com
首页 > 编程语言 > 详细

C#中WinForm控件的跨线程更新Invoke

时间:2015-11-17 19:30:32      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:delegate invoke winform c#

目的:

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



本文出自 “技术改变生活” 博客,请务必保留此出处http://btvnlue.blog.51cto.com/9578271/1713391

C#中WinForm控件的跨线程更新Invoke

标签:delegate invoke winform c#

原文地址:http://btvnlue.blog.51cto.com/9578271/1713391

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!