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

目的:

用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();
            }
        }
时间: 2024-12-18 20:38:09

C#中WinForm控件的跨线程更新Invoke的相关文章

WPF中窗口控件的跨线程调用

原文:WPF中窗口控件的跨线程调用 在WinForm中,我们要跨线程访问窗口控件,只需要设置属性CheckForIllegalCrossThreadCalls = false;即可. 在WPF中要麻烦一下,同样的不允许跨线程访问,因为没有权限,访问了会抛异常: 没有CheckForIllegalCrossThreadCalls 属性,怎么办? 在WPF中的窗口控件都有一个Dispatcher属性,允许访问控件的线程:既然不允许直接访问,就告诉控件我们要干什么就好了. 方法如下: private

(转).NET 4.5中使用Task.Run和Parallel.For()实现的C# Winform多线程任务及跨线程更新UI控件综合实例

http://2sharings.com/2014/net-4-5-task-run-parallel-for-winform-cross-multiple-threads-update-ui-demo 在C# WINFORM的开发中,难免会遇到多线程的开发以提高程序的执行效率.自己刚才开始在做多线程的开发时也遇到了很多这方面的问题,比如:如何使用并实现多线程功能.跨线程更新UI控件等问题.还记得最初使用的是System.Threading命名空间下的Thread类来实现的: C# 1 2 3

关于Winform中控件的跨线程访问

闲着没事想起来用winform做一个随机的抽号程序,咋看来这么个东西其实并不难,不过对于一个菜鸟来说其实并不简单!尤其是对于多线程不是特别熟悉的新手来说. 首先,界面比较简单winform,(图片上传好麻烦~~!) 既然是随机抽取号码,就得有随机数(其实关键不在这里,图省事就random了) 可是在用到多线程的时候问题就出现了: 无法跨线程访问label控件,无法修改label的text. 网上找了很多资料,感觉例子搞得难以理解,遂自己撸起.... 1.有人这么写: // Control.Che

C# Winform 跨线程更新UI控件常用方法总结(转)

出处:http://www.tuicool.com/articles/FNzURb 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种: 1. 通过UI线程的SynchronizationContext的Post/Send方法更新: 2. 通过UI控件的Invoke/BegainInvoke方法更新: 3. 通过BackgroundWorker取代Thre

C# Winform 跨线程更新UI控件常用方法汇总

C# Winform 跨线程更新UI控件常用方法汇总 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种:1. 通过UI线程的SynchronizationContext的Post/Send方法更新:2. 通过UI控件的Invoke/BeginInvoke方法更新: 3. 通过BackgroundWorker取代Thread执行异步操作:4. 通过设置窗体

在WinForm中使用委托来在其他线程中改变控件的显示

假设winform中有两个控件: 1.ListView用来显示进度的文本提示,ID:listView_progressInfo 2.ProgressBar用来显示进度,ID:progressBar1 在此winform的后台.cs文件中声明两个公用委托类型:ControlChanger, ProgressChanger public delegate void ControlChanger(string progressText); public delegate void ProgressCh

使用winform控件注意线程绘制界面冲突

在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写: void ButtonOnClick(object sender,EventArgs e) { button.Text="关闭"; } 这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”.注意这里是“可能”,并不一定会触发该种异常

Winfrom 跨线程更新控件

来源:http://www.cnblogs.com/rainbowzc/archive/2010/09/29/1838788.html 由于多线程可能导致对控件访问的不一致,导致出现问题.C#中默认是要线程安全的,即在访问控件时需要首先判断是否跨线程,如果是跨线程的直接访问,在运行时会抛出异常. 解决办法有两个: 1.不进行线程安全的检查 2.通过委托的方式,在控件的线程上执行 常用写法:(不安全) private void WriteToolStripMsg(string msg, Color

winform窗体中查找控件

private RichTextBox FindControl()        { RichTextBox ret = null;            try            {                Control[] controls = Application.OpenForms["MainForm"].Controls.Find("txtContent", false);                if (controls != nul