首先,先说明为什么要使用多线程来控制串口收发信息。我们知道在Winform和WPF内,界面线程是主线程,如果你在主线程控制串口收发信息的话,会导致页面假死,给客户不良好的使用体验,因此多线程控制串口通信是为优化客户使用体验而生的。
在微软官方提供的类库里,有很多方法可以实现这一操作,在这篇博文中,我主要介绍使用AutoResetEvent来实现这一操作。
当然我后续的博文里我也会提供使用Task相关类库来实现这一操作。
那么AutoResetEvent的主要应用场景就是两个线程间的"阻塞通信",可以通过AutoResetEvent简单的在一个线程里面控制另一个线程的阻塞,使用起来及其方便,但是需要注意AutoResetEvent的状态的变化控制!
废话不多说,上代码!(AutoResetEvent来自Threading命名空间,如果使用高版本比如.NET 6注意自行手动添加引用)
首先定义AutoResetEvent,构造函数里的false是他默认的状态。
private AutoResetEvent resetEvent = new AutoResetEvent(false);
然后我们默认你已经开发完毕了发送命令,我们新建一个函数,用于执行串口发送命令:
private void SendMessage()
{
byte[] data = null;
StringBuilder sb_0x10 = new StringBuilder();
if (SerialPortCtrler.Instance.SendCommand((byte)Convert.ToInt32(Convert.ToInt32(model_0x10.ID.ToString(), 16)), data))
{
sb_0x10.Append("发送模块复位成功,");
sb_0x10.Append(resetEvent.WaitOne(Const.waitOneTime) ? "并收到下位机回复。" : "但未收到下位机回复");
//这是用于显示界面信息的消息队列,不是本文所介绍的重点可忽略。
//queueState.Enqueue(sb_0x10.ToString());
}
else
{
//queueState.Enqueue("发送模块复位失败");
}
}
这里我们注意WaitOne方法是一个类似于阻塞等待的方法,当线程执行到此步后,会等待WaitOne方法中传入的毫秒数,在收到Set信号后,此AutoResetEvent的状态会被置为true,否则会一直等待到时间结束,当到达限定时间仍没收到Set的信号时,此WaitOne会返回false。所以需要注意的是此函数需要由另一个线程来执行:
thread = new Thread(SendPhotonCal); thread.Start();
那么发送我们代码编写完毕了,如何处理串口接收呢?其实很简单,这里我是使用的win32的消息通知机制来处理消息的接收(这里如果不了解消息通知的小伙伴可以使用轮询来做个实验,这里不再赘述),然而这并不是重点,重点是如何处理这个AutoResetEvent的waitone等待。不过其实应用代码也很简单,仅一句话:
resetEvent.Set();
public override void OnMessage(MessageParameters msg) { bool result = false;switch (msg.Msg) { case (int)Command.Reset: result = BitConverter.ToInt32(((byte[])msg.Param), 2) == 0; if (result) { strTemp = "模块复位成功!"; resetEvent.Set(); } else { errorCode = BitConverter.ToInt32(((byte[])msg.Param).Reverse().ToArray(), 2).ToString("X"); strTemp = "模块复位失败" + errorCode; resetEvent.Set(); } break;
}
}
这时一个简单的多线程串口收发程序就编写完毕了。
需要注意的是AutoResetEvent常用的是三个函数,这里提及了两个,另一个是Reset(),一般情况下,如果是单线程使用了AutoResetEvent在waitone之前是需要Reset重置信号状态的。