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

使用System.IO.Ports的C# WinForms应用运行40分钟后崩溃求助

问题分析与解决方案

问题概述

基于C# WinForms开发的应用,使用EasyModbus库通过RS485 RTU协议从多个从站采集数据,运行约40分钟后崩溃,抛出System.NullReferenceException,报错堆栈指向System.IO.Ports的后台事件循环线程。

报错信息

System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=System.IO.Ports
StackTrace:
at System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents(Object state)
at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
at System.Threading.Thread.StartCallback()

读写核心代码

public int[]? ReadMultipleRegisters(byte nodeID, int startAddress, int quantity)
{
    int[] res=null;
    int repeat = 0;
    _mut.WaitOne();
repeat:
    repeat++;
    try
    {
        _modbusClient.UnitIdentifier = nodeID;
        res = _modbusClient.ReadHoldingRegisters(startAddress, quantity);
    }
    catch (Exception ex)
    {
        Debug.Write("Error during Reading multiple registers, repeat:"+repeat+" nodeID:"+nodeID+" stAdr:"+startAddress+" quantity:"+ quantity);
        Debug.WriteLine(ex.ToString());
        if (repeat != _repeats)
        {
            goto repeat;
        }
        if(repeat==_repeats)
        {
            Debug.WriteLine("!#!#!#!#!#READING FAILED!#!#!##!#!#!##!");
        }
    }
    finally
    {
        _mut.ReleaseMutex();
    }
    if(repeat>1)
    {
        Debug.WriteLine(repeat+"MORE THAN ONE REPEAT!!!!!!!!!!!");
    }
    return res;
}

public void WriteSingleRegister(byte id, int address, int value) {
    try
    {
        _mut.WaitOne();
        _modbusClient.UnitIdentifier = id;
        _modbusClient.WriteSingleRegister(address, value);
    }
    catch (Exception ex) 
    {
        //todo ob napaki
        Debug.Write("Error during Writing to single register, ");
        Debug.WriteLine(ex.ToString());
    }
    finally { 
        _mut.ReleaseMutex();
    }
}

可能的原因

  1. 串口对象生命周期异常:_modbusClient对应的串口连接被意外释放或置为null,但后台的串口事件循环线程仍在尝试调用相关方法,触发空引用。比如重连时未正确停止原有串口监听就直接替换实例。
  2. 线程安全漏洞:虽然用Mutex保护了读写操作,但_modbusClient可能在其他线程(如重连逻辑)被修改,导致读写过程中实例变为null。
  3. EasyModbus库封装隐患:该库对串口的后台线程处理可能存在线程安全问题,长时间运行后触发空引用。
  4. 底层串口异常未处理:硬件故障、端口断开等底层异常未被捕获,导致串口内部状态损坏,最终引发空引用。

解决方案

1. 保障_modbusClient生命周期安全

  • 禁止在运行中随意替换或销毁_modbusClient实例。若需重连,先调用_modbusClient.Disconnect(),等待后台线程停止后再创建新实例。
  • 在读写方法开头增加实例有效性检查:
    if (_modbusClient == null || !_modbusClient.Connected)
    {
        Debug.WriteLine("Modbus client is disconnected");
        return null; // 写方法直接return
    }
    

2. 完善全局线程安全保护

  • 对_modbusClient的创建、销毁、重连操作,也要用同一个Mutex保护,避免读写过程中实例被篡改。
  • 确保所有串口操作都在同步上下文内执行,禁止非受控线程直接修改串口对象。

3. 全局捕获未处理异常

由于异常发生在System.IO.Ports的后台线程,普通try-catch无法捕获,需注册全局异常处理事件:

// 应用启动时注册
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
    var ex = e.ExceptionObject as Exception;
    Debug.WriteLine($"Unhandled Exception: {ex?.ToString()}");
    // 此处可添加日志记录、自动重连等恢复逻辑
};

Application.ThreadException += (sender, e) =>
{
    Debug.WriteLine($"UI Thread Exception: {e.Exception.ToString()}");
    // 处理UI线程异常
};

4. 优化读写逻辑

替换goto为循环结构,提升代码可维护性,同时强化异常处理:

public int[]? ReadMultipleRegisters(byte nodeID, int startAddress, int quantity)
{
    int[] res = null;
    int repeatCount = 0;
    bool isSuccess = false;

    while (repeatCount < _repeats && !isSuccess)
    {
        _mut.WaitOne();
        try
        {
            if (_modbusClient == null || !_modbusClient.Connected)
            {
                Debug.WriteLine("Modbus client disconnected");
                break;
            }
            _modbusClient.UnitIdentifier = nodeID;
            res = _modbusClient.ReadHoldingRegisters(startAddress, quantity);
            isSuccess = true;
        }
        catch (Exception ex)
        {
            repeatCount++;
            Debug.WriteLine($"Read failed (repeat {repeatCount}): NodeID={nodeID}, Start={startAddress}, Quantity={quantity}, Ex={ex}");
        }
        finally
        {
            _mut.ReleaseMutex();
        }
    }

    if (!isSuccess)
    {
        Debug.WriteLine("!#!#!#!#!#READING FAILED!#!#!##!#!#!##!");
    }
    else if (repeatCount > 0)
    {
        Debug.WriteLine($"{repeatCount + 1} attempts needed for successful read");
    }

    return res;
}

5. 检查硬件与串口配置

  • 确认RS485转换器稳定性,排查接线松动问题,避免通信过程中硬件断开。
  • 调整串口超时时间、波特率等参数,匹配从站设备配置,减少通信异常概率。

内容的提问来源于stack exchange,提问作者Matej Blaško

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 04:18:30