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

C#多线程场景下EventHandler Invoke调用偶发不触发问题

C# 多HMIMaster实例事件偶发不触发问题修复

问题根因

代码存在3处明确的编写错误,直接导致事件偶发不触发:

  • 事件绑定时机存在竞态条件:实例创建后先调用Connect()、SetRegisters()方法(这两个方法大概率会直接启动后台轮询线程执行ReadReg逻辑),之后才绑定OnEvent事件处理方法。通信启动后到事件绑定完成前的时间窗口内,所有触发的ReadReg调用拿到的事件委托都是未绑定处理方法的null,自然不会执行HMIEvent中的逻辑,这就是ReadReg内日志能正常打印、事件处理日志缺失的核心原因。
  • 事件委托拷贝时机错误:你在ReadReg方法刚进入、还未执行读寄存器操作时就提前把OnEvent赋值给了handler局部变量,哪怕后续读操作耗时期间你绑定了事件,handler里存的还是之前的null,调用时不会触发任何处理方法。
  • 集合线程安全问题:多线程场景下直接用非线程安全的List<T>做_masters的Add、取Last值操作,可能出现事件绑定到错误实例、绑定操作丢失的问题。

修正方案

  • 调整实例初始化顺序:先创建HMIMaster实例、绑定事件,再执行连接、寄存器配置等会启动后台通信的操作,从根源上堵上事件绑定的时间窗口。
  • 把事件委托的局部拷贝逻辑移动到实际Invoke调用前,保证拿到最新绑定的委托列表。
  • 多线程访问_masters集合时加锁,或者直接替换为ConcurrentBag<HMIMaster>这类线程安全集合,避免集合操作竞态。

修正后代码

HMIMaster类ReadReg方法修正

// 自定义委托重命名避免和系统自带EventHandler重名混淆
public delegate void HMIEventHandler(HMIEventArgs e);
public event HMIEventHandler OnEvent;

private void ReadReg(RegisterMap register)
{
    if (!_hmiConnected) return;
    var eventArgs = new HMIEventArgs();

    try
    {
        byte slaveId = 1;
        ushort startAddress = register.Register;

        eventArgs.Event = Events.GotData;
        eventArgs.IPAddress = _hostname;
        eventArgs.Name = register.FriendlyName;

        ushort[] val = _master.ReadHoldingRegisters(slaveId, startAddress, 1);
        eventArgs.Data = val[0];
        
        // 委托拷贝移到Invoke前,获取最新绑定的处理方法
        var handler = OnEvent;
        handler?.Invoke(eventArgs);

        Debug.WriteLine("Got data from " + _hostname + ":" + register.Register + "(" + register.FriendlyName + ") : " + val[0]);
        
    }
    catch (Exception err)
    {
        Debug.WriteLine(err.ToString());                
    }
}

实例创建与绑定逻辑修正

new Thread(() =>
{
    tasks.Add(Task.Factory.StartNew(() =>
    {
        // 先实例化、先绑事件,再启动通信
        var hmiInstance = new HMIMaster();
        hmiInstance.OnEvent += HMIEvent;
        hmiInstance.Connect(ipAddress, port).SetRegisters(registers);

        // 加锁保护非线程安全集合的多线程操作
        lock (_masters)
        {
            _masters.Add(hmiInstance);
            Debug.WriteLine(_masters.Count + " masters");
        }
    }));
}).Start();


private static void HMIEvent(HMIEventArgs e)
{
    Debug.WriteLine(">> in logger (" + e.IPAddress + " " + e.Event + ") >> " + e.Name + " :: " + e.Data);
    var handler = OnEvent;
    handler?.Invoke(e);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:18:30