C# 界面定时器控件Timer的回调函数中有问题代码影响ui刷新效率的问题


今天继续研究“多线程读plc内存“时发现一个问题。

我弄了一个定时器,以300ms间隔时间,执行读plc的4个内存地址的工作。

然后,拖动窗口时明显感觉很卡。

image.png

timer中写了4条输出信息到控件上的语句。

每条输出控件的语句都使用了BeginInvoke方式的委托。

  private void timer1_Tick(object sender, EventArgs e)
        {
            var plc = new PlcReadWrite();
            outMsg1($"{plc.Read(0, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
            outMsg2($"{plc.Read(1, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
            outMsg3($"{plc.Read(2, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
            outMsg4($"{plc.Read(3, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
        }

outMsg的码如下:

  private void outMsg1(string msg)
        {
            list555.Add(msg);
            if (txt1.InvokeRequired)
            {
                Action<string> actionDelegate = (x) => { txt1.Text = msg; };
                this.txt1.BeginInvoke(actionDelegate, str);
            }
            else
            {
                txt1.Text = msg;
            }
        }



如果注释掉后面三条outMsg,只留一条。

拖动窗口不再卡顿。
这说明4条outMsg语句的总时间超过了Interval的间隔时间。引发了Timer的阻塞。

Timer阻塞了,ui线程就阻塞了,所以拖动窗口就会卡顿。


但是我把定时器控件的Interval属性改为3000, 则拖动窗体时,在3秒钟内不卡顿,但是3秒后会卡顿一下、正常3秒、再卡顿一下,依次循环。

4条outMsg执行的总时间是不可能超过3秒的。
这又是为什么呢?


看一下性能报告。

可以看到application.run独占86.31,属于烧起来了状态。

image.png


然而定时器的事件函数timer1_Tick能耗却不高。算起来占1.4%。

这一点让勇哥有点意外,原本以为耗能全部在timer1_Tick中。

image.png


勇哥查了一下 Application.Run的作用。

从MSDN中查看了一下Application.Run()函数的定义--
"在当前线程上开始运行标准应用程序消息循环。"
用Reflector查看了一下Application.Run()的实现代码,如下所示:

Public Shared Sub Run()
      ThreadContext.FromCurrent.RunMessageLoop(-1, New ApplicationContext)
End Sub

详细见:https://blog.csdn.net/u012780337/article/details/85452988



可见实际上代码Application.Run耗能93.9%,是因为消息循环发生了阻塞。

勇哥认为正是timer1_Tick的回调函数中的不当操作引发了阻塞。

image.png


既然是因为消息循环阻塞的原因,那么试试在timer1_Tick中的plc.Read函数中加入Application.DoEvents() 会怎么样?

(Application.DoEvents执行后立刻处理所有的当前在消息队列中的Windows消息。)

image.png

结果并没有效果,问题依旧。


试着在timer1_Tick中加入线程,如下:

   private void timer1_Tick(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                var plc = new PlcReadWrite();

                outMsg1($"{plc.Read(0, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
                outMsg2($"{plc.Read(1, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
                outMsg3($"{plc.Read(2, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
                outMsg4($"{plc.Read(3, Guid.NewGuid(), Thread.CurrentThread.ManagedThreadId).ToString()}");
            });
        }

结果问题解决了!!

想想这是为什么呢?


勇哥想了一下,原因如下:

我们来看一下outMsg函数的写法。

我们之前直接在timer1_Tick中调用outMsg,因为timer1_Tick是工作在UI线程上的,所以调用outMsg,会执行

else的代码,即执行 txt1.Text=msg;

而我们在timer1_Tick中加入Task线程后,因为它不是ui线程,所以调用outMsg,会执行 if(txt1.InvokeRequired)的代码。

这部分代码正是我们希望的BeginInvoke。

我们知道BeginInvoke是异步的,它会再开一个线程完成自己的委托控件显示的任务。

这个相当于4个outMsg1都是异步方式进行控件更新了,也就不影响消息循环了。

  private void outMsg1(string msg)
        {
            list555.Add(msg);
            if (txt1.InvokeRequired)
            {
                Action<string> actionDelegate = (x) => { txt1.Text = msg; };
                this.txt1.BeginInvoke(actionDelegate, str);
            }
            else
            {
                txt1.Text = msg;
            }
        }



--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864