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

RS485 Modbus通信触发Object reference未实例化异常求助

异常原因分析与解决方案

异常根源定位

你遇到的Object reference not set to an instance of an object异常来自.NET底层串口处理逻辑,触发场景是后台串口事件线程尝试访问已被释放的SerialStream对象,结合代码和使用的Modbus库,主要原因有以下几点:

1. 串口断开时的后台事件未被正确终止

调用modbusClient.Disconnect()时,EasyModbus库底层的SerialPort对象被关闭,但SerialStream的后台EventLoopRunner线程可能仍在运行并尝试处理接收事件。此时该线程访问的串口资源已被释放,直接引发空引用异常。

2. 异步代码与同步阻塞的混合滥用

在GetData方法中,通过readTask.ContinueWith(...).Wait()阻塞主线程等待异步操作完成,这种写法会导致线程上下文混乱:

  • 后台串口操作线程与主线程的资源调度冲突,加速串口资源的异常释放
  • ContinueWith的回调执行线程不受控,若涉及资源访问,更容易引发线程安全问题

3. 频繁创建销毁ModbusClient的资源泄漏风险

每次读取都创建新的ModbusClient并执行连接/断开操作,频繁的串口资源申请与释放会导致底层串口驱动的资源管理混乱,增加后台线程资源未清理的概率。


针对性解决方案

1. 安全终止串口后台事件

修改finally块中的断开逻辑,尝试先禁用串口事件再断开连接(若EasyModbus库允许通过反射访问底层SerialPort):

finally
{
    if (modbusClient != null && modbusClient.Connected)
    {
        // 反射获取底层SerialPort并取消事件订阅
        var serialPort = modbusClient.GetType().GetField("serialPort", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(modbusClient) as SerialPort;
        if (serialPort != null)
        {
            serialPort.DataReceived -= modbusClient.OnDataReceived;
            serialPort.ErrorReceived -= modbusClient.OnErrorReceived;
        }
        modbusClient.Disconnect();
    }
    _lock.Release();
}

如果库不允许反射访问,可在断开前增加短暂延迟,给后台线程足够时间处理收尾:

modbusClient.Disconnect();
await Task.Delay(100); // 给后台线程留时间终止事件循环

2. 重构异步调用逻辑

将GetData改为异步方法,用await替代ContinueWith + Wait,避免阻塞线程:

public async Task<List<RawInput>> GetDataAsync(DateTime date)
{
    var data = new List<RawInput>();
    var items = GetSensors();
    var groupeItems = items.GroupBy(i => i.SlaveID).ToList();

    foreach (var itemGroup in groupeItems)
    {
        var sensors = itemGroup.ToList();
        var sensor = sensors.OrderBy(i => i.InputCh).LastOrDefault();
        if (sensor == null) continue;
        
        try
        {
            var result = await modbusManager.ReadInputRegistersAsync(
                $"COM{sensor.SerPort}", 9600, (byte)itemGroup.Key, 0, sensor.InputCh);
            
            data.AddRange(sensors.ConvertAll(s => new RawInput()
            {
                ItemId = s.Id,
                RawValue = result[s.InputCh - 1]
            }));
        }
        catch (Exception ex)
        {
            Trace.WriteLine($"读取SlaveID {itemGroup.Key} 失败: {ex.Message}");
            data.AddRange(sensors.ConvertAll(s => new RawInput()
            {
                ItemId = s.Id,
                RawValue = 0
            }));
        }
    }
    return data;
}

3. 复用ModbusClient实例

避免每次读取都创建新客户端,针对每个串口维护一个单例ModbusClient,减少资源频繁创建销毁:

// 维护串口客户端的缓存字典
private readonly Dictionary<string, ModbusClient> _clientCache = new Dictionary<string, ModbusClient>();
private readonly object _cacheLock = new object();

private ModbusClient GetOrCreateClient(string comPort, int baudrate, byte unitIdentifier)
{
    lock (_cacheLock)
    {
        if (_clientCache.TryGetValue(comPort, out var client) && client.Connected)
        {
            client.UnitIdentifier = unitIdentifier; // 更新从站ID
            return client;
        }
        
        // 创建新客户端并缓存
        var newClient = new ModbusClient(comPort)
        {
            UnitIdentifier = unitIdentifier,
            Baudrate = baudrate,
            Parity = Parity.None,
            StopBits = StopBits.One,
            ConnectionTimeout = 1000
        };
        newClient.Connect();
        _clientCache[comPort] = newClient;
        return newClient;
    }
}

注意:需要在应用退出时遍历缓存,断开所有客户端连接。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 14:05:34