使用队列实现串口实时读写
好久没写博客了。上一次还是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 之线程安全集合篇