背景
在 OpenTAP 里,「资源」不只是万用表或示波器,任何需要「打开→使用→关闭」的实体都被抽象成 IResource。Resource Manager(下文简称 RM)就是幕后管家:它负责按需启停、冲突检测、引用计数,还要保证 TestPlan 无论正常结束还是异常取消,仪器都能安全下电。官方文档只告诉你“把仪器拖进测试计划就好”,却没人讲 RM 怎么知道何时该 Open()、何时该 Close()。今天把源码拆到一行行,把这个黑盒照亮。
框架分析
RM 的核心代码集中在 Engine/ResourceManager.cs,对外只暴露三个关键 API:
Open(CancellationToken token)– 拓扑排序后按依赖顺序打开资源。Close()– 逆序关闭,引用计数归零才真正Close()。GetResource<T>()– 查询已缓存实例,支持按类型、名称、接口多重过滤。
底层用两张表:
Dictionary<IResourceNode, ResourceWrapper> _wrappers– 节点 → 包装器,包装器里存引用计数、实例对象、打开状态。List<IResourceNode> _openedInOrder– 按打开先后记录,保证关闭时逆序。
实现过程
1. 拓扑打开:防止「A 依赖 B,却先开 A」的尴尬
1 | // ResourceManager.Open 节选 |
TopologicalSort 用 DFS 实现,时间复杂度 O(V+E),保证依赖资源先就绪。
2. 引用计数:同一路由被多个 Step 复用也不重复开关
1 | // ResourceManager.GetResource<T> 节选 |
3. 逆序关闭:异常场景也能安全下电
1 | // ResourceManager.Close 节选 |
注意事项
- 自己写 Step 时,不要把
IResource存成字段后就不管,一旦 TestPlan 被提前取消,RM 只会调Close(),不会帮你Dispose();需要一次性清理请实现IDisposable并在Close()里调用。 - 若资源构造函数里就抛异常,RM 会立即触发
Close()已打开的部分,但不会回滚构造函数副作用——如果你在构造函数里把仪器状态改了,记得自己捕获并还原。 - 命名重复不会编译期报错,RM 在运行期按「先匹配类型→再匹配名称」策略,可能拿到意料之外的实例;保证仪器别名全局唯一最省心。
可复现命令
以下最小示例演示 RM 的引用计数行为:
1 | # 克隆示例(已含最小插件) |
日志里能看到:
- 第一条 Step 打开电源,RefCount=1
- 第二条 Step 复用,RefCount=2
- 计划结束一次性 Close,RefCount 归零才真正下电
小结
Resource Manager 的源码不到 600 行,却把「依赖排序」「引用计数」「异常安全」三件事做得干净利落:拓扑保证顺序,计数避免重复,逆序+try/catch 保证异常也不漏关。下次再拖仪器到测试计划,你知道背后有人帮你数着引用、排着队、守着最后一盏灯熄灭。
关键源码路径
Engine/ResourceManager.cs– 核心实现Engine/IResource.cs– 资源接口定义Engine/IResourceNode.cs– 计划节点与资源绑定Engine/ResourceSettings.cs– 资源别名、序列化配置