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

Unity中OPC UA客户端OnNotification仅调试模式触发问题求助

问题分析与解决思路

你的核心问题出在Session生命周期管理和线程调度上,以下是具体问题点和修复方案:

1. Session被提前释放(最关键原因)

你用using块包裹Session创建,Start方法执行完毕后,using会自动调用session.Dispose()销毁连接。只有调试时断点延迟了using块的结束,Session还存活,才能收到通知。

修复:

把Session、Subscription改成类成员变量,移除using块,让连接长期存活:

private Session _opcSession;
private Subscription _opcSubscription;

void Start()
{
    Debug.Log("Create a session");
    // 补全缺失的config初始化
    var config = new ApplicationConfiguration
    {
        ApplicationName = "UnityOPCClient",
        ApplicationType = ApplicationType.Client,
        SecurityConfiguration = new SecurityConfiguration
        {
            AutoAcceptUntrustedCertificates = true // 测试环境用,生产环境需配置证书验证
        },
        TransportConfigurations = new TransportConfigurationCollection(),
        TransportQuotas = new TransportQuotas { OperationTimeout = 60000 },
        ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
    };
    config.Validate(ApplicationType.Client).GetAwaiter().GetResult();

    // 创建Session,不再用using自动释放
    _opcSession = Session.Create(config, new ConfiguredEndpoint(null, new EndpointDescription(endpointurl)), 
        true, "", 60000, null, null).GetAwaiter().GetResult();

    // 后续Browse、订阅逻辑改用类成员变量
    Debug.Log("Browse the server namespace.");
    ReferenceDescriptionCollection refs;
    byte[] cp;
    _opcSession.Browse(null, null, ObjectIds.ObjectsFolder, 0u, BrowseDirection.Forward, 
        ReferenceTypeIds.HierarchicalReferences, true, 
        (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out cp, out refs);

    Debug.Log("Create a subscription");
    _opcSubscription = new Subscription(_opcSession.DefaultSubscription) 
    { 
        PublishingInterval = 1000, 
        PublishingEnabled = true, 
        KeepAliveCount = 5 
    };
    _opcSession.AddSubscription(_opcSubscription);
    _opcSubscription.Create();

    Debug.Log("Add monitored items to the subscription.");
    var alphaItem = new MonitoredItem(_opcSubscription.DefaultItem) 
    { 
        DisplayName = "alpha", 
        StartNodeId = "ns=2;s=Alpha", 
        SamplingInterval = 1000 
    };
    alphaItem.Notification += OnNotification;
    var betaItem = new MonitoredItem(_opcSubscription.DefaultItem) 
    { 
        DisplayName = "beta", 
        StartNodeId = "ns=2;s=Beta", 
        SamplingInterval = 1000 
    };
    betaItem.Notification += OnNotification;

    Global.list.Add(alphaItem);
    Global.list.Add(betaItem);

    _opcSubscription.AddItems(Global.list);
    _opcSubscription.ApplyChanges();

    // 初始读值
    Debug.Log("Read Values of Monitored items.");
    foreach (var item in Global.list)
    {
        var v = _opcSession.ReadValue(item.StartNodeId);
        int val = Convert.ToInt16(v.Value);
        Global.values.Add(val);
    }
}

2. Update中重复订阅事件

你在Update里每次都给MonitoredItem绑定OnNotification,会导致同一个事件被绑定多次,触发时重复执行还会造成内存泄漏。直接删除Update里的这段代码:

Global.list.ForEach(i => i.Notification += OnNotification);

3. 后台线程回调的主线程适配

OPC UA的OnNotification事件在后台线程触发,Unity的Debug.Log虽能跨线程输出,但修改全局变量或操作游戏对象时会有线程安全问题,建议把逻辑切到主线程执行:

实现主线程调度:

private Queue<Action> _mainThreadActions = new Queue<Action>();

void Update()
{
    lock (_mainThreadActions)
    {
        while (_mainThreadActions.Count > 0)
        {
            _mainThreadActions.Dequeue().Invoke();
        }
    }
}

private void OnNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
    try
    {
        MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
        if (notification == null) return;

        // 将逻辑封装到Action,加入主线程队列
        lock (_mainThreadActions)
        {
            _mainThreadActions.Enqueue(() =>
            {
                Debug.Log("Notification: " + notification.Message.SequenceNumber + " \" " + monitoredItem.ResolvedNodeId + " \" and Value = " + notification.Value);
                Debug.Log(monitoredItem.DisplayName + " = " + notification.Value);

                string value = Convert.ToString(notification.Value);
                var y = Global.list.FindIndex(x => x.DisplayName == monitoredItem.DisplayName);
                if (y >= 0)
                {
                    int val = Convert.ToInt16(value);
                    Global.values[y] = val;
                }
            });
        }
    }
    catch (Exception ex)
    {
        Debug.Log(string.Format("OnMonitoredItemNotification error: {0}", ex.Message));
    }
}

4. 资源清理

在脚本销毁时关闭Session和Subscription,避免资源泄漏:

void OnDestroy()
{
    if (_opcSubscription != null)
    {
        _opcSubscription.Delete();
        _opcSubscription = null;
    }
    if (_opcSession != null)
    {
        _opcSession.Close();
        _opcSession.Dispose();
        _opcSession = null;
    }
}

额外注意点

  • 之前代码中config变量未初始化,必须补全ApplicationConfiguration配置,否则Session创建会失败。
  • 生产环境不要开启AutoAcceptUntrustedCertificates,需配置正规的证书验证逻辑,避免安全风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 18:46:01