You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 09:48:13