今天继续研究“多线程读plc内存“时发现一个问题。
我弄了一个定时器,以300ms间隔时间,执行读plc的4个内存地址的工作。
然后,拖动窗口时明显感觉很卡。
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,属于烧起来了状态。
然而定时器的事件函数timer1_Tick能耗却不高。算起来占1.4%。
这一点让勇哥有点意外,原本以为耗能全部在timer1_Tick中。
勇哥查了一下 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的回调函数中的不当操作引发了阻塞。
既然是因为消息循环阻塞的原因,那么试试在timer1_Tick中的plc.Read函数中加入Application.DoEvents() 会怎么样?
(Application.DoEvents执行后立刻处理所有的当前在消息队列中的Windows消息。)
结果并没有效果,问题依旧。
试着在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
版权声明:本文为博主原创文章,转载请附上博文链接!

