背景
OpenTAP 的资源(Instrument、DUT、ResultListener)在测试执行前需要自动打开,但真实工程里资源之间常常互相引用:A 依赖 B,B 又可能间接依赖 A。若全都按“先依赖后打开”的串行规则处理,很容易在循环依赖场景里陷入等待链。OpenTAP 的做法不是简单报错,而是通过 ResourceOpenBehavior 把依赖分成强依赖与弱依赖,在可并行处主动并行,降低死锁风险。
框架分析
这套机制分两层:
- 依赖分析层:
ResourceDependencyAnalyzer扫描资源属性,读取[ResourceOpen(...)]特性。默认是Before(强依赖),InParallel会进入弱依赖,Ignore则直接跳过。 - 调度执行层:
ResourceTaskManager为每个资源创建异步打开任务。强依赖通过Task.WaitAll保证顺序;弱依赖不阻塞当前Open(),而是在后续finallyTasks中等待后再触发ResourceOpened事件。
这个分层很关键:分析层决定图结构,调度层决定等待策略,职责清晰,调试时也容易定位到底是“依赖声明问题”还是“线程等待问题”。
实现过程
执行入口在 BeginStep(..., TestPlanExecutionStage.Open/Execute, ...),它先汇总 StaticResources + EnabledSteps,再调用 BeginOpenResources 批量拉起任务。每个资源的 OpenResource 逻辑是:先等强依赖完成,再执行 Resource.Open(),最后处理弱依赖完成后的回调。
可复现实验(验证循环依赖在并行标注下可运行):
1 | cd /home/ops/clawd/repos/opentap |
该用例在 parallel=true 与 parallel=false 下分别断言不同 Verdict,正好对应 InParallel 对循环引用行为的影响。
注意事项
InParallel只适合“打开阶段不要求对方已完全可用”的依赖;若Open()内必须立刻访问对端状态,仍应使用默认强依赖。Ignore会让引用资源不参与自动开关,适合手工生命周期管理,但也最容易造成“看起来配置了资源却没被打开”的误判。- 弱依赖的等待被放到后置任务中,是为避免循环等待;若你在
ResourceOpened里做重操作,仍可能拉长整体启动时间。 - 资源从设置中被删除但仍被引用时,
BeginOpenResources会直接抛错,别把它当成普通空指针处理。
小结
ResourceOpenBehavior 的价值不在“多一个枚举”,而在于它把资源依赖从单一拓扑排序升级成“强约束 + 弱约束”的混合调度模型。对复杂仪器链路来说,这比一味串行更稳,也比全并行更可控。实战里建议先用默认 Before,只对确认安全的环路边标注 InParallel,这样最不容易踩坑。
关键源码路径:
Engine/ResourceTaskManager.csEngine/ResourceDependencyAnalyzer.csEngine.UnitTests/ResourceDependencyTests.csEngine/TestPlanRun.cs