好久沒寫博客了。上一次還是2015年。。。。
寫C#同時讀寫串口,也就是發送讀取、寫入到串口,之前的做法,定時循環讀取,又需要的地方寫入,而且加lock
1 public void Read(){ 2 while(true){ 3 lock(this) 4 SerialPort.Write(讀取的命令內容1); 5 Thread.sleep(500); 6 lock(this) 7 SerialPort.Write(讀取的命令內容2); 8 Thread.sleep(500); 9 //..可能還有.... 10 } 11 12 } 13 14 public void Write1(){ 15 lock(this) 16 SerialPort.Write(寫入的命令內容); 17 } 18 19 public void Write2(){ 20 lock(this) 21 SerialPort.Write(寫入的命令內容); 22 }
后來,參考網上的前輩思路,用隊列,生產者/消費者模式,把需要寫入串口的命令放到隊列里(Quere<T>),用一個線程專門負責用隊列里取出數據並寫入串口,這個思路不錯。試了一下
這里主要是用modbus slave工具,模擬串口工具VSPD做一組模擬串口,slave工具初始化,C#去讀寫數據。
一開始隊列使用
1 private static Queue<byte[]> cmdList = new Queue<byte[]>();
讀取/寫入的入隊
1 /// <summary> 2 /// 讀取寄存器狀態,200ms讀一次 3 /// </summary> 4 /// <returns></returns> 5 static async Task Read() 6 { 7 while (true) 8 { 9 //從0,讀10個寄存器 10 byte[] send = new byte[] 11 { 12 0x01,0x03,0,0,0,0x0A 13 }; 14 Enqueue(send); 15 await Task.Delay(200); 16 } 17 } 18 19 /// <summary> 20 /// 寫寄存器操作 21 /// </summary> 22 /// <returns></returns> 23 static async Task Write() 24 { 25 for (short i = 0; i < 10; i++) 26 { 27 var index = BitConverter.GetBytes(i); 28 29 for (short j = 0; j < 10; j++) 30 { 31 var data = BitConverter.GetBytes(j); 32 byte[] send = new byte[] { 33 0x01,0x06,index[1],index[0],data[1],data[0] 34 }; 35 Enqueue(send); 36 await Task.Delay(1000); 37 }; 38 } 39 } 40 41 /// <summary> 42 /// 添加進隊列 43 /// </summary> 44 /// <param name="send"></param> 45 private static void Enqueue(byte[] send) 46 { 47 cmdList.Enqueue(send); 49 }
取出,並發送
/// <summary> /// 從隊列里取出內容並發送 /// </summary> /// <returns></returns> static async Task Loop() { while (true) { if (cmdList.Count > 0) { var send = cmdList.Dequeue; //發送操作 var sendByte = crc16(send); sp.Write(sendByte, 0, sendByte.Length);
await Task.Delay(50);//加上這句似乎就可以正常運行了。。。。之前沒加才有了接下來的內容。 } else { await Task.Delay(50); } } }
然后Task.Run(async()=>{Loop();});似乎就可以了,由於每次發送完我沒加Delay(),導致我在slave工具中看到黏包的現象
一次收到2條C#發來的命令,一度懷疑發送的時候,命令就是連一起發的,隊列里面的內容被2個線程同時添加了命令,帶着這個疑問,問了老朋友,結果他發來一個地址,講的《C#中線程安全集合篇》。
線程安全的集合類,
.net framework4新引入的五個專門支持多線程添加和刪除操作而設計的集合類型。不同於以前版本的中集合類型中的SyncRoot屬性 以及 Synchronized()方法,這些新類型使用了高效的鎖定和免鎖定同步機制
ConcurrentQueue(T)
ConcurrentStack(T)
ConcurrentDictionary(TKey, TValue)
ConcurrentBag(T)
BlockingCollection(T)
后來改了代碼,使用的是BlockingCollection(T)
1 static BlockingCollection<byte[]> cmdListBlocking = new BlockingCollection<byte[]>(); 2 3 /// <summary> 4 /// 添加進隊列 5 /// </summary> 6 /// <param name="send"></param> 7 private static void Enqueue(byte[] send) 8 { 9 cmdListBlocking.Add(send); 10 } 11 12 //取出來,並發送 13 Task.Factory.StartNew(async () => 14 { 15 foreach (var vale in cmdListBlocking.GetConsumingEnumerable()) 16 { 17 //發送操作 18 var sendByte = crc16(vale); 19 sp.Write(sendByte, 0, sendByte.Length); 20 await Task.Delay(50);//依然要加Delay()... 21 } 22 });
不加Task.Delay(50)還是會出現黏包的命令,可能是串口發送數據緩沖區?如果同時進行2個 Task.Run(async()=> { await Write(); });,不加Delay(),是寫不上數據的,哪位大神告訴是什么原因呢,對串口發送數據機制還是不夠理解。。。
做個備份
參考:https://www.cnblogs.com/chengxiaohui/articles/5672768.html C# 4.0 之線程安全集合篇