分享

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

 长江黄鹤 2024-03-12 发布于湖北

前言

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

示例代码

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3. progressBar1.Visible = true;
  4. Task task = new Task(() =>
  5. {
  6. int i = 0;
  7. while (++i < 100)
  8. {
  9. Thread.Sleep(10);//模拟耗时操作
  10. MethodInvoker mi = new MethodInvoker(() =>
  11. {
  12. progressBar1.Value = i;
  13. this.label1.Text = i.ToString();
  14. });
  15. this.BeginInvoke(mi);
  16. }
  17. });
  18. task.Start();
  19. task.ContinueWith(t => {
  20. progressBar1.Visible = false;
  21. },TaskScheduler.FromCurrentSynchronizationContext());
  22. }

线程的延续采用ContinueWith解决

BeginInvoke解决界面的刷新问题

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

  1. private void button2_Click(object sender, EventArgs e)
  2. {
  3. Task task1 = new Task(() =>
  4. {
  5. M1();
  6. MethodInvoker mi = new MethodInvoker(() =>
  7. {
  8. this.label1.Text = "1";
  9. });
  10. this.BeginInvoke(mi);
  11. M2();
  12. mi = new MethodInvoker(() =>
  13. {
  14. this.label1.Text = "2";
  15. });
  16. this.BeginInvoke(mi);
  17. });
  18. task1.Start();
  19. this.label1.Text="主线程开始运行!" ;
  20. }
  21. private void M1()
  22. {
  23. Thread.Sleep(2000);
  24. }
  25. private void M2()
  26. {
  27. Thread.Sleep(1000);
  28. }

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多