温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2020-07-16 11:14:01 来源:网络 阅读:5420 作者:btvnlue 栏目:编程语言

目的:

    用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
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI