C#--耗时操作实现UI界面实时更新不阻塞(耗时操作解决窗体卡顿)

文章介绍了如何在C#WinForm应用中使用Task和MethodInvoker来实现窗体加载时的进度条或百分比实时显示。通过Task进行异步操作避免UI线程阻塞,MethodInvoker确保在主线程中安全更新控件状态,同时展示了BeginInvoke用于界面刷新以及如何处理跨线程访问的异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

C#实现窗体加载进度条或者百分比实时显示耗时操作的进度,方法有很多。但是经过我的学习、查找与实际应用,发现Task配合MethodInvoker最为高效便捷。下面我就来结合代码讲一下要注意的问题。

基础知识

C#在winform上进行耗时操作往往会放置progressbar,问题是在UI线程上进行耗时操作就会导致UI线程阻塞,界面就会卡顿。所以势必要另开一个线程进行耗时操作,之后将耗时操作的过程实时反馈给UI线程即可,可问题是新开的线程向UI线程传递数据的时候,就会出现经典报错:

InvalidOperationException,并提示消息:“从不是创建控件的线程访问它。

这是因为NET原则上禁止跨线程访问。因为这样可能造成错误的发生,有一种简单粗暴的方法是禁止编译器对跨线程访问作检查,Control.CheckForIllegalCrossThreadCalls = false;可以实现访问,但是什么时候出错不敢保证。

Task

Task是一个升级版本的Thread的类,它非常的灵活,支持取消、阻塞等待、合并、多个Task协同操作......。总之使用Task编码高效易懂,你基本不用去研究Thread与ThreadPool了,虽然本质上还是这个。我个人理解Task就是对Thread的再次封装。

task

MethodInvoker

MethodInvoker 是位于System.Windows.Forms下的元数据,表示一个委托,该委托可以执行托管代码中声明为void且不接受任何参数的任何方法。在对控件的 invoke 方法进行调用时或需要一个简单委托又不想自己定义时可以使用该委托。 我是这样理解的,在新线程中使用MethodInvoker 委托执行耗时操作, 其实相当于是在主线程中执行的, 这样就避免了 跨线程访问控件

methodinvoker

示例代码

 private void button1_Click(object sender, EventArgs e)
        {
            progressBar1.Visible = true;
            Task task = new Task(() =>
            {
                int i = 0;
                while (++i < 100)
                {
                    Thread.Sleep(10);//模拟耗时操作
                    MethodInvoker mi = new MethodInvoker(() =>
                    {
                        progressBar1.Value = i;
                        this.label1.Text = i.ToString();
                    });
                    this.BeginInvoke(mi);
                }
            });
            task.Start();
            task.ContinueWith(t => {
                progressBar1.Visible = false;
            },TaskScheduler.FromCurrentSynchronizationContext());
            
        }

线程的延续采用ContinueWith解决

BeginInvoke解决界面的刷新问题

TaskScheduler.FromCurrentSynchronizationContext() 解决跨线程访问报错

 private void button2_Click(object sender, EventArgs e)
        {
            Task task1 = new Task(() =>
            {
                M1();
                MethodInvoker mi = new MethodInvoker(() =>
                {
                   
                    this.label1.Text = "1";
                });
                this.BeginInvoke(mi);

                M2();
                mi = new MethodInvoker(() =>
                {
                   
                    this.label1.Text = "2";
                });
                this.BeginInvoke(mi);
            });
            task1.Start();
          
            this.label1.Text="主线程开始运行!" ;
        }

        private void M1()
        {
            Thread.Sleep(2000);
        }
        private void M2()
        {
            Thread.Sleep(1000);
        }

button2的方式可以在task线程中按顺序执行耗时操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韦_恩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值