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

WPF中SerialPort ReadChar()方法随机超时问题求助

RS232串口接收随机超时问题排查与解决

问题描述

我开发的WPF程序通过RS232发送数据,用独立函数接收,已短接RS232引脚2与引脚3做自环测试。运行时调用SerialPort.ReadChar()会随机抛出**"The operation has timed out."**错误,但实际数据能正常接收并打印。测试中70%-90%场景运行正常,调整ReadTimeout与WriteTimeout为500毫秒后情况改善,但仍会随机出现超时问题。

核心代码实现

  1. 类中定义SerialPort与委托:
SerialPort _serialPort;
public delegate void ReceiveData(object sender);
  1. InitializeComponent()后注册DataReceived事件:
_serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort1_DataReceive);
  1. 事件处理方法:
private void SerialPort1_DataReceive(object sender, SerialDataReceivedEventArgs e)
{
    Dispatcher.BeginInvoke(new ReceiveData(OutputDataToList), sender);
}
  1. 委托执行方法:
private void OutputDataToList(object sender)
{
    SerialPort sp = (SerialPort)sender;
    _inChar = '1';
    while (_inChar != '\n')
    {
        try
        {
            _inChar = (char)sp.ReadChar();
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error while receiving the serial port. " + ex.Message);
            break; 
        }
        if (_inChar != '\n')
        {
            _inMessage += _inChar;
        }
    }
    if (_inMessage != "")
    {
        lstReads.Items.Add(_inMessage);
        lstReads.SelectedIndex = lstReads.Items.Count - 1;
    }
    _inMessage = "";
}

问题原因分析

  1. DataReceived事件触发逻辑:该事件仅在串口有数据到达时触发,但此时可能只有部分数据(未到换行符\n)。代码在UI线程循环调用ReadChar()等待换行,若期间无新数据,就会触发超时。
  2. UI线程阻塞影响:通过Dispatcher.BeginInvoke将读取逻辑放到UI线程执行,UI线程本身可能有其他渲染或操作,导致串口读取响应延迟,增加超时概率。
  3. ReadChar()的阻塞特性:ReadChar()是阻塞式方法,当串口缓冲区为空时会一直等待直到超时。循环依赖\n终止,一旦某次循环时缓冲区无数据,就会抛出超时异常。

解决方案

方案1:先读全缓冲区数据,再拆分换行

放弃循环等待单个字符,一次性读取当前缓冲区所有数据,再按换行符拆分处理完整行,保留不完整数据等待下一次接收:

private void OutputDataToList(object sender)
{
    SerialPort sp = (SerialPort)sender;
    try
    {
        // 读取缓冲区所有可用数据
        string receivedData = sp.ReadExisting();
        // 拼接之前未完成的片段
        _inMessage += receivedData;
        
        // 拆分完整行并更新UI
        string[] lines = _inMessage.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        foreach (string line in lines)
        {
            lstReads.Items.Add(line);
            lstReads.SelectedIndex = lstReads.Items.Count - 1;
        }
        
        // 保留未完成的片段(无换行的部分)
        _inMessage = _inMessage.EndsWith("\n") ? "" : _inMessage.Substring(_inMessage.LastIndexOf('\n') + 1);
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error while receiving the serial port. " + ex.Message);
    }
}

方案2:后台线程读数据,UI线程仅做更新

把数据读取逻辑放到后台线程(DataReceived事件本身就是后台线程触发),仅将UI更新操作抛到UI线程,避免阻塞:

private void SerialPort1_DataReceive(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    string receivedData = "";
    try
    {
        receivedData = sp.ReadExisting();
    }
    catch (Exception ex)
    {
        Dispatcher.BeginInvoke(() => MessageBox.Show("Error while receiving the serial port. " + ex.Message));
        return;
    }
    
    // 仅将数据处理和UI更新抛到UI线程
    Dispatcher.BeginInvoke(() => ProcessReceivedData(receivedData));
}

private void ProcessReceivedData(string receivedData)
{
    _inMessage += receivedData;
    string[] lines = _inMessage.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    foreach (string line in lines)
    {
        lstReads.Items.Add(line);
        lstReads.SelectedIndex = lstReads.Items.Count - 1;
    }
    
    _inMessage = _inMessage.EndsWith("\n") ? "" : _inMessage.Substring(_inMessage.LastIndexOf('\n') + 1);
}

方案3:优化阻塞式读取逻辑

如果必须保留循环读字符的方式,读取前先检查缓冲区是否有数据,避免无意义的等待超时:

private void OutputDataToList(object sender)
{
    SerialPort sp = (SerialPort)sender;
    _inChar = '1';
    while (_inChar != '\n')
    {
        // 缓冲区无数据时短暂等待,避免立即触发超时
        if (sp.BytesToRead == 0)
        {
            Thread.Sleep(10);
            continue;
        }
        
        try
        {
            _inChar = (char)sp.ReadChar();
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error while receiving the serial port. " + ex.Message);
            break; 
        }
        
        if (_inChar != '\n')
        {
            _inMessage += _inChar;
        }
    }
    
    if (_inMessage != "")
    {
        lstReads.Items.Add(_inMessage);
        lstReads.SelectedIndex = lstReads.Items.Count - 1;
    }
    _inMessage = "";
}

内容的提问来源于stack exchange,提问作者Jonathan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:42:50