C# .NET串口DataReceived触发时BytesToRead返回0的问题排查
串口读取Arduino数据时DataReceived触发但BytesToRead为0的问题分析与解决
问题场景
编写C#程序通过串口读取Arduino输出的数据(Arduino使用serial.println发送),运行中出现DataReceived事件触发后,port.BytesToRead却返回0的情况,导致错误信息输出,同时干扰主轮询循环的数据读取流程。
用户提供的核心代码如下:
串口初始化与事件绑定
_serialPort = new SerialPort(); _serialPort.PortName = "COM7";//Set your board COM _serialPort.BaudRate = 9600; _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; _serialPort.DtrEnable = true; _serialPort.RtsEnable = true; _serialPort.DataReceived += Watch_BLE_DataReceived; private void Watch_BLE_DataReceived(object sender, EventArgs arg) { SerialPort port = sender as SerialPort; if (port == null) { return; } if (!(port.BytesToRead > 0)) { // print error } }
主轮询循环
while (true) { try { if (!_serialPort.IsOpen) { _serialPort.Open(); } } catch (Exception e) { continue; } try { if (_serialPort.BytesToRead > 0) { // process read data } } }
原因分析
- 线程竞态冲突:
DataReceived事件在独立线程触发,而主轮询循环在主线程执行。当事件触发的瞬间,主循环可能已经抢先读取了串口缓冲区的所有数据,导致事件中BytesToRead为0。 - 事件触发的非数据场景:
SerialPort的DataReceived事件并非仅在新数据到达时触发,串口状态变化(如DTR/RTS信号波动、连接临时中断恢复)也可能触发事件,此时无实际数据,BytesToRead自然为0。 - 小数据块被快速消费:若Arduino发送的数据量极小(如单字符),主循环轮询频率过高时,会在事件线程调度的间隙将数据读完,导致事件触发时缓冲区已空。
解决办法
1. 统一数据读取入口
同时使用事件和轮询两种方式读取数据必然引发冲突,需二选一:
- 完全通过
DataReceived事件处理数据读取,主循环仅负责维护串口连接:
修改事件处理代码:
优化主循环:private void Watch_BLE_DataReceived(object sender, EventArgs arg) { SerialPort port = sender as SerialPort; if (port == null || !port.IsOpen) return; try { // 读取一行数据(对应Arduino的println输出) string receivedData = port.ReadLine(); // 处理读取到的数据 } catch (TimeoutException) { // 处理数据未完整接收的超时情况 } }while (true) { try { if (!_serialPort.IsOpen) { _serialPort.Open(); } } catch (Exception e) { // 记录连接错误,延迟后重试避免频繁报错 Thread.Sleep(1000); continue; } // 让出CPU资源,避免空转占用过高 Thread.Sleep(10); }
2. 增加事件的容错判断
保留两种读取方式的前提下,移除无意义的错误输出,直接跳过无数据的事件触发:
private void Watch_BLE_DataReceived(object sender, EventArgs arg) { SerialPort port = sender as SerialPort; if (port == null || !port.IsOpen) return; if (port.BytesToRead <= 0) { // 无数据直接返回,不输出错误 return; } // 读取所有可用数据 string receivedData = port.ReadExisting(); // 处理数据 }
3. 配置事件触发阈值
通过ReceivedBytesThreshold设置触发DataReceived事件的最小字节数,减少无数据时的无效触发:
在串口初始化代码中添加:
// 根据Arduino发送的单条数据最小长度调整,比如每行至少包含5字节则设为5 _serialPort.ReceivedBytesThreshold = 1;
内容的提问来源于stack exchange,提问作者universe123
相关产品推荐
相关产品推荐

