首先,先說明為什么要使用多線程來控制串口收發信息。我們知道在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重置信號狀態的。