跳到主要内容

Safe API — 永不抛异常变体

概述

OPC UA 服务端在网络抖动 / 节点删除 / 权限变更等情况下会让 SDK 抛 OpcUaException。 UI 主线程和批处理 (上千节点循环 Read) 不希望被一条失败中断, 所以 SDK 为常用 RPC 各提供一个 Safe 版本 — 任何异常都折叠为 StatusCode 返回, 业务侧用元组解构判读, 不再写 try/catch

对应规范段:

  • Browse — Part 4 §5.8.2
  • Read — Part 4 §5.10.2
  • Write — Part 4 §5.10.4
  • Call — Part 4 §5.11.2
  • HistoryRead — Part 11 §6.5

API

方法类别读写说明
BrowseSafe(nodeId, filter)浏览返回 (StatusCode, IReadOnlyList<OpcUaReference>)
ReadSafe(nodeId, attribute)返回 (StatusCode, DataValue), 失败时 Value=null
WriteSafe(nodeId, value, attribute)返回 StatusCode
CallSafe(objectId, methodId, inputs)方法返回 (StatusCode, IReadOnlyList<Variant>)
ReadHistorySafe(nodeId, t0, t1, max)历史返回 (StatusCode, IReadOnlyList<DataValue>)

返回值约定:

  • StatusCode == Good — 业务成功, 数据有效
  • StatusCode == Bad... — 失败, 数据集合为空 / Value=null, 不会 throw
  • 内部异常 (反射 / native 层 crash) → 兜底 BadInternalError (0x80020000)

代码示例

using DarraOpcUa_Client;

using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Connect();

// 1) BrowseSafe — UI 树异步刷新, 节点被删不让全树报错
var (st, refs) = ua.BrowseSafe("ns=2;s=Boiler1");
if (st != StatusCode.Good)
Console.WriteLine($"Browse 失败 {st}, 跳过此分支"); // 例如 BadNodeIdUnknown
else
foreach (var r in refs) Console.WriteLine(r.BrowseName);

// 2) ReadSafe — 批量轮询时不让单个失败打断
foreach (var nid in nodeList)
{
var (rs, dv) = ua.ReadSafe(nid);
if (rs == StatusCode.Good && dv != null)
using (dv) Console.WriteLine($"{nid} = {dv.Value}");
else
Console.WriteLine($"{nid} = <{rs}>");
}

// 3) WriteSafe — 批量下发参数
foreach (var (nid, val) in setpoints)
{
var rc = ua.WriteSafe(nid, new Variant(val));
if (rc != StatusCode.Good) Console.WriteLine($"写 {nid} 失败 {rc}");
}

// 4) CallSafe — 调用方法, 不允许异常吞掉用户的"按钮点击"
var (cst, outs) = ua.CallSafe("ns=2;s=Robot", "ns=2;s=Robot.Home");
if (cst != StatusCode.Good) MessageBox.Show($"Home 失败: {cst}");

// 5) ReadHistorySafe — 历史曲线一段失败不让整个对话框崩
var (hst, vals) = ua.ReadHistorySafe("ns=2;s=Temp", t0, t1, maxValues: 5000);

与重试机制的关系

普通 Read/Write/Browse/Call/ReadHistory 内部都包了 RetryOnSessionFault, session 级故障 (BadSessionClosed) 会自动重连一次再重试。 Safe 变体调用的也是同一套核心方法, 所以 Safe 也会受益于自动重连, 只是把"重连后仍然失败"的异常折叠为 StatusCode。

最佳实践

  • UI 主线程一律用 Safe — UI 不允许异常逃逸, 否则进程挂掉
  • 批处理 / 巡检循环用 Safe — 单点失败不阻塞整体
  • 业务关键路径 (例如启停设备前的预检) 仍用普通版本, 让异常显式暴露, 由上层决定 retry / abort
  • 不要 if (status == Good) ... else throw — 那等于把 Safe 变回不安全, 直接用普通版本即可

跨语言对照

C#PythonJavaC++RustC
BrowseSafebrowse_safebrowseSafeBrowseSafebrowse_safeDarraUa_Session_BrowseSafe
ReadSaferead_safereadSafeReadSaferead_safeDarraUa_Session_ReadSafe
WriteSafewrite_safewriteSafeWriteSafewrite_safeDarraUa_Session_WriteSafe
CallSafecall_safecallSafeCallSafecall_safeDarraUa_Session_CallSafe
ReadHistorySaferead_history_safereadHistorySafeReadHistorySaferead_history_safeDarraUa_Session_ReadHistorySafe

下一步