C#串口发送消息后读取速度降至每秒1字节的问题排查
问题排查与修复建议
一、串口读取逻辑与超时问题
1. 无效数据阻塞读取流程
用户消息处理后,串口缓冲区可能残留无效字节(如0x00),后续tick读取时,ReadByte()会因等待有效数据触发超时(若超时设为1秒,就会表现为每秒1字节的读取速度)。
修复方案:在读取响应前先过滤无效前置字节,直到找到合法的0xA5协议头:
// 替换Commander.SendCLinkBytes中读取header的逻辑 List<byte> reply = new List<byte>(); try { Debug.WriteLine("Commander.SendCLinkBytes(): Looking for valid header"); // 先过滤无效前置字节 while (commPort.BytesToRead > 0) { byte preByte = (byte)commPort.ReadByte(); if (preByte == 0xA5) { reply.Add(preByte); break; } else { Debug.WriteLine("Commander: Skipping invalid byte: 0x" + preByte.ToString("X2")); } } if (reply.Count == 0) { Debug.WriteLine("Commander: No valid header found, aborting read"); SetCommsAreBusy(false); return message; } // 继续读取UnitID、Length等后续字段... } catch(Exception ex) { Debug.WriteLine("Commander: Read error: " + ex.Message); SetCommsAreBusy(false); return message; }
2. 不合理的超时设置
检查commPort.ReadTimeout的值,若设置为1000毫秒,无数据时ReadByte()会阻塞1秒才返回。建议将超时调整为200毫秒,避免长时间阻塞:
// 初始化串口时设置 commPort.ReadTimeout = 200;
二、线程同步与竞态条件问题
1. 定时器回调中的不必要阻塞
DoPreTick中通过Task.Wait()阻塞线程,会导致定时器线程被占用,影响串口读写的响应速度。直接同步调用ProcessNextUserMessage()即可:
// 替换DoPreTick中的Task启动逻辑 if (bOutboxFullToken && !bIsProcessingUserMessage && !bOutboxProcessedToken) { Debug.WriteLine("DoPreTick()" + counter + ": Processing user message: " + outbox.ToBytes()[3]); ProcessNextUserMessage(); // 直接同步执行,无需Task.Wait() }
2. 状态标志的线程安全
bIsProcessingUserMessage、bOutboxFullToken等变量被多线程访问,需用volatile修饰保证可见性,或用锁包裹读写操作:
private volatile bool bIsProcessingUserMessage; private volatile bool bOutboxFullToken; private volatile bool bOutboxProcessedToken;
3. TaskCompletionSource未触发
SendCLinkMessage中创建TaskCompletionSource后未触发,会导致线程永久阻塞。在ProcessNextUserMessage末尾添加触发逻辑:
// ProcessNextUserMessage最后一行 userMessageTCS?.SetResult(true);
三、CommsAreBusy状态管理漏洞
SendCLinkBytes的catch块未重置CommsAreBusy状态,若读取抛出异常,会导致后续所有串口操作被阻塞:
catch(Exception ex) { Debug.WriteLine("Commander: Sending bytes timed out waiting for reply to: " + BitConverter.ToString(message.ToBytes())); SetCommsAreBusy(false); // 新增:重置状态 }
四、调试建议
- 每次读取前打印
commPort.BytesToRead,观察缓冲区数据量变化; - 跟踪
CommsAreBusy的状态切换日志,确认无永久锁定情况; - 用Visual Studio线程窗口监控定时器线程、用户消息线程的阻塞状态。
内容的提问来源于stack exchange,提问作者jahwarp
相关产品推荐
相关产品推荐

