Project.cs 完整解析 — 流程执行引擎线程模型—Process()后台常驻线程 AutoResetEvent信号驱动跑一次RunOnce或连续RunCycleMermaid 时序图展示 UI/线程/模块三层交互三个数据容器—ModuleList(有序列表) →ModuleDic(字典 O(1)查找, CollectionChanged 自动同步) →OutputMap(三层字典: 模块→变量→值, 全局变量池)Execute() 主调度— 9 步流程拆解构建树→起始模块→while循环→断点→ExeModule()→循环计数→UI更新→LogicMethod跳转→耗时记录。前缀跳转标记的作用原理LogicMethod()— 项目最复杂的150行代码。三个分支详细展开条件分支: if→跳过→else if→跳过→else→结束的跳转逻辑附模块列表示意图循环工具: 循环开始 flagfalse → 跳过循环体 → 循环结束 → 跳回循环开始的回环机制停止循环: 深度嵌套中找到父级循环结束并跳出Convert2ModuleNameTreeNode()— Stack 模拟作用域的树形构建算法附扁平列表→嵌套树的完整转换示例 eIndexStack嵌套循环索引独立计数Home 命门—SysHomeDone锁机制Home 流程执行前所有其他流程阻塞等待变量管理—AddOutputParam(写入特殊类型处理) 和GetParamByName(解析模块.变量 全局变量特殊路径) 的完整源码分析文件:Se rvices\Project.cs(844行)角色: JGTechVision 的心脏 — 流程调度、模块执行、变量管理、条件分支、循环控制线程模型: 后台常驻线程 AutoResetEvent 信号驱动标注: 文件中LogicMethod前有注释:“以下逻辑十分复杂 实现了各种循环工具和条件分支深度嵌套跳转 不要轻易修改任何地方!!!”1. 架构总览Solution │ ┌──────┴──────┐ │ 持有 N 个 │ │ Project │ └──────┬──────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ Project A Project B Project C (采集流程) (检测流程) (汇总流程) │ ┌───────┼────────┐ ▼ ▼ ▼ 线程控制 模块列表 输出映射每个Project包含:容器类型作用ModuleListObservableCollectionModuleBase模块有序列表, 驱动 DataGrid/TreeView UIModuleDicDictionarystring, ModuleBase模块名→模块实例, O(1)查找OutputMapDictionarystring, Dictionarystring, VarModel模块名→(变量名→变量), 全局变量池BaseTreeNodeModuleNameTreeNode模块树形结构的根节点2. 数据容器详解2.1 OutputMap — 三层字典OutputMap ├── 模板匹配 → { X: VarModel(123.4), Y: VarModel(67.9), 分数: VarModel(0.98) } ├── 测量圆 → { 直径: VarModel(10.02), 圆心X: VarModel(320), 状态: VarModel(true) } └── C#脚本 → { SN: VarModel(ABC), IsOK: VarModel(true) }每个模块执行完后通过AddOutputParam()写入, 其他模块通过GetParamByName()读取。2.2 ModuleDic — 自动同步publicvoidModuleList_CollectionChanged(...){ModuleDic.Clear();foreach(variteminModuleList)ModuleDic.Add(item.ModuleParam.ModuleName,item);}ModuleList增删时自动重建ModuleDic— 保证两者永远同步。2.3 ModuleNameTreeNode — 树形结构 (仅运行时)classModuleNameTreeNode{publicModuleNameTreeNodeParent;publicstringName;// 当前节点模块名publicListstringChildList;// 子节点模块名列表}每次执行前由Convert2ModuleNameTreeNode()构建, 用于支持嵌套的循环/条件跳转。3. 线程模型 — 后台常驻 信号驱动模块执行Process线程UI线程模块执行Process线程UI线程构造函数 → new Thread(Process) → Start()alt[RunOnce][RunCycle]while(true) { WaitOne() 阻塞 }Start() → ThreadStatustrue → m_AutoResetEvent.Set()被唤醒!Execute() → 遍历 ModuleListExeModule() 返回 boolThreadStatusfalse → 回到 WaitOne 阻塞Thread.Sleep(50) → 再来一轮// 构造函数中启动后台线程publicProject(){m_ThreadnewThread(Process);m_Thread.IsBackgroundtrue;// 后台线程, 主程序退出时自动结束m_Thread.Start();}4. Process() — 后台主循环 (396→458行)privatevoidProcess(){while(true){if(ThreadStatusfalse){m_AutoResetEvent.WaitOne();// ★ 阻塞, 等待 Start() 唤醒}else{// RunOnce 或 RunCycle → 执行if(RunModeRunOnce||RunModeRunCycle){// Home 流程: 先执行, 其他流程等 Home 完成if(ProjectInfo.ProcessNameHome){MainViewModel.Ins.SysHomeDonefalse;Execute();// 执行 Home 回零流程}else{if(MainViewModel.Ins.SysHomeDone)// Home 完成后才执行Execute();}// UI 设计器刷新if(ProjectInfo.IsRefreshUi)UIDesignView.UpdateUIDesign();}// 根据运行模式决定下一步switch(RunMode){caseNone:ThreadStatusfalse;break;// 回到阻塞caseRunOnce:ThreadStatusfalse;// 执行完一次 → 回到阻塞RunModeNone;break;caseRunCycle:Thread.Sleep(50);break;// 连续运行, 等50ms再来}}}}Home 流程的特殊性: “Home” 是回零/初始化的专用流程名 — 其他流程必须等 Home 执行完毕 (SysHomeDonetrue) 才能启动。5. Execute() — 模块调度核心 (477→581行)publicvoidExecute(stringmoduleName){// ① 构建树形结构 (循环/条件嵌套需要)Convert2ModuleNameTreeNode();// ② 确定起始模块: 默认第一个, 或指定名称ExeModuleNamestring.IsNullOrEmpty(moduleName)?ModuleList[0].ModuleParam.ModuleName:moduleName;TotalTime.Clear();// ③ ★ 主循环: 遍历模块直到 ExeModuleName 为空while(ExeModuleName!){// 快速模式下跳过UI更新和延时if(!Solution.Ins.QuickMode)Thread.Sleep(6);if(ThreadStatusfalse)break;// 外部 Stop 打断// ④ 断点处理if(moduleParam.IsEnableBreakPoint){Breakpoint.Reset();BreakpointFlagtrue;ContinueRunFlagfalse;}if(BreakpointFlag!ContinueRunFlag)Breakpoint.WaitOne();// 阻塞, 等待用户继续// ⑤ ★ 执行当前模块HTimerhTimernewHTimer(true);ModuleDic[ExeModuleName].ModuleParam.StatuseRunStatus.Running;ModuleDic[ExeModuleName].CancelWaitfalse;flagModuleDic[ExeModuleName].ExeModule();// ⑥ 循环计数处理 (循环开始模块)if(ExeModuleName.StartsWith(循环开始)){// 首次运行 → 重置 pIndex -1// 每次 pIndex, 超过 CyclicCount → flagfalse 退出循环moduleParam.pIndex;if(moduleParam.CyclicCountmoduleParam.pIndex)flagtrue;// 继续循环else{moduleParam.pIndex-1;flagfalse;// 循环完成}}// ⑦ UI 更新 事件发布if(!Solution.Ins.QuickMode){EventMgr.Ins.GetEventModuleOutChangedEvent().Publish();ProcessView.Ins.UpdateStatus(moduleParam);}// ⑧ 确定下一个要执行的模块if(!ExeModuleName.StartsWith())// 开头 跳转标记{LogicMethod(refmoduleParam,flag,refIsNextModuleUpdate);if(!IsNextModuleUpdate)// 逻辑工具没有改 → 顺序执行下一个{intindexGetModuleIndexByName(ExeModuleName);if(indexModuleList.Count-1)ExeModuleNameModuleList[index1].ModuleParam.ModuleName;elseExeModuleName;// 最后一个 → 结束}}else{ExeModuleNameExeModuleName.Substring(1);// 去掉 前缀Thread.Sleep(20);}// ⑨ 记录耗时hTimer.Stop();TotalTime.Add(hTimer.GetMilliSecond);}ProcessViewModel.Ins.ProcessTimeTotalTime.Sum();}6. LogicMethod() — 条件分支与循环的深度嵌套 (582→735行)这是整个项目最复杂的逻辑。对三种特殊插件类型做跳转处理:6.1 条件分支 (PluginName “条件分支”)模块列表: [如果A, 模块1, 否则如果B, 模块2, 否则, 模块3, 结束, 后续模块] 执行 如果A: ├── flagtrue → 执行模块1 → 跳过否则如果否则→ 跳到结束 └── flagfalse → 跳过模块1 → 跳转到否则如果B 执行 否则如果B: ├── flagtrue → 执行模块2 → 跳到结束 └── flagfalse → 跳转到否则 执行 否则: → 执行模块3 → 结束 执行 结束: → 继续下一个模块if(flagfalse(name.StartsWith(如果)||name.StartsWith(否则如果))){// 向前查找下一个 否则如果/否则/结束intcurIndexlogicList.IndexOf(ExeModuleName);for(inticurIndex1;ilogicList.Count();i){if(logicList[i].StartsWith(否则如果)||logicList[i].StartsWith(否则)||logicList[i].StartsWith(结束)){ExeModuleNamelogicList[i];// ★ 跳转!IsNextModuleUpdatetrue;break;}}}6.2 循环工具 (PluginName “循环工具”)模块列表: [循环开始, 模块1, 模块2, 循环结束, 后续模块] 循环开始 (flagfalse 或 IsUsefalse): → 跳过循环体 → 直接跳到 循环结束 循环结束: → 跳回 循环开始 (IsNextModuleUpdatetrue → 从循环开始重新执行)6.3 停止循环 (PluginName “停止循环”)在循环体内部触发: → 找到父级循环开始的循环结束 → 跳到循环结束下一个模块 → 同时将循环开始的 pIndex 重置6.4 尾部处理 — 跳过否则如果/否则// 如果当前模块执行成功,// 且下一个模块是否则如果或否则// → 直接跳到结束 (跳过所有后续分支)if(pluginName!条件分支||flagtrue){if(nextModuleName.StartsWith(否则如果)||nextModuleName.StartsWith(否则)){// 向上查找最近的结束节点for(inticurIndex1;ilogicList.Count();i){if(logicList[i].StartsWith(结束)){ExeModuleNamelogicList[i];IsNextModuleUpdatetrue;break;}}}}7. Convert2ModuleNameTreeNode() — 树形结构构建 (736→828行)将扁平的ModuleList转换为嵌套的树形结构, 核心是用Stack 模拟作用域:StackModuleNameTreeNodestacknewStackModuleNameTreeNode();foreach(variteminModuleList){stringnameitem.ModuleParam.ModuleName;// ① 遇到结束类节点 → Pop 出栈 (作用域闭合)if(name.StartsWith(结束)||name.StartsWith(否则)||name.StartsWith(循环结束)||name.StartsWith(坐标补正结束)){stack.Pop();}// ② 挂到栈顶节点的子列表中 (嵌套关系)if(stack.Count0){stack.Peek().ChildList.Add(name);node.Parentstack.Peek();}else{BaseTreeNode.ChildList.Add(name);// 顶层→挂到根}ModuleTreeNodeMap.Add(name,node);// ③ 遇到开始类节点 → Push 入栈 (新作用域)if(name.StartsWith(如果)||name.StartsWith(否则)||Regex.IsMatch(name,补正开始[0-9]*$)||Regex.IsMatch(name,循环开始[0-9]*$)||Regex.IsMatch(name,文件夹开始[0-9]*$)||Regex.IsMatch(name,执行片段[0-9]*$)){stack.Push(node);}}示例转换:扁平 ModuleList: [如果A, 模块1, 否则, 模块2, 结束, 循环开始1, 模块3, 循环结束, 后续] ↓ Convert2ModuleNameTreeNode ↓ 树形结构: Root ├── 如果A (stack[如果A]) │ ├── 模块1 │ ├── 否则 (Pop 如果A → Push 否则) │ │ └── 模块2 │ └── 结束 (Pop 否则) ├── 循环开始1 (stack[循环开始1]) │ ├── 模块3 │ └── 循环结束 (Pop 循环开始1) └── 后续循环索引专用逻辑— 额外维护eIndexStack:if(name.StartsWith(循环结束))eIndexStack.Pop();elseif(name.StartsWith(循环开始))eIndexStack.Push(moduleParam.pIndex);elsemoduleParam.pIndexeIndexStack.Count0?eIndexStack.Peek():-1;这使得嵌套循环中的嵌套索引始终指向正确的循环层级 — 内层循环和外层循环的pIndex各自独立。8. 其他关键方法Start / StoppublicvoidStart(){RunModeeRunMode.RunOnce;// 或 RunCycle (由 Solution 设置)ThreadStatustrue;// 唤醒 Process 线程m_AutoResetEvent.Set();}publicvoidStop(){RunModeeRunMode.None;ThreadStatusfalse;// Process 线程检测到后回到 WaitOne}AddOutputParam — 变量写入publicvoidAddOutputParam(ModuleParammoduleParam,stringvarName,stringvarType,objectobj,stringnote){// 首次写入: 创建 VarModelif(!OutputMap[moduleParam.ModuleName].ContainsKey(varName))dic.Add(varName,newVarModel{NamevarName,DataTypevarType,Valueobj,Notenote});// 重复写入: 只更新 Value (支持 RImage/HImage/HRegion 等特殊类型)elsedic[varName].Valueobj;}特殊类型处理:RImage/HImage/HRegion直接赋值引用, 普通类型用Convert。GetParamByName — 变量读取publicVarModelGetParamByName(stringlinkName){// 解析 模块名.变量名 格式string[]arrlinkName.Split(.);stringmoduleNamearr[0].Substring(1);// 去掉 前缀stringvarNamearr[1];// 全局变量特殊处理: 从 Solution.Ins.SysVar 查if(moduleName全局变量)returnSolution.Ins.SysVar.FirstOrDefault(oo.NamevarName);// 普通变量: 从 OutputMap 查returnOutputMap[moduleName]?[varName];}ModuleList_CollectionChanged// ModuleList 增删时自动触发 → 重建 ModuleDicforeach(variteminModuleList)ModuleDic.Add(item.ModuleParam.ModuleName,item);9. 完整执行流程时序用户点击运行 │ ▼ Solution.StartRun() → CurrentProject.RunMode eRunMode.RunOnce → CurrentProject.Start() → ThreadStatustrue → m_AutoResetEvent.Set() │ ▼ Process() 线程被唤醒 │ ├→ Execute() │ ├→ Convert2ModuleNameTreeNode() // 构建树 │ ├→ ExeModuleName ModuleList[0] // 从第一个开始 │ │ │ ├── while (ExeModuleName ! ) │ │ ├→ ModuleDic[name].ExeModule() // ★ 执行模块 │ │ ├→ LogicMethod() // 条件/循环跳转 │ │ ├→ AddOutputParam() // 写入输出 │ │ ├→ EventMgr.Publish(ModuleOutChangedEvent) │ │ └→ ExeModuleName 下一个模块 (或跳转到指定模块) │ │ │ └── ExeModuleName → 流程结束 │ ├→ RunMode RunOnce → ThreadStatusfalse → 回到 WaitOne └→ RunMode RunCycle → Thread.Sleep(50) → 再来一轮10. 设计总结特点说明后台常驻线程每个 Project 构造时就启动线程, 通过信号量唤醒, 避免频繁创建线程Home 命门“Home” 流程执行前锁住所有其他流程, 完成后统一释放树形跳转基于 Stack 的树形结构 同层 ChildList 遍历, 实现 if/else/while 的深度嵌套断点支持BreakpointFlagBreakpoint.WaitOne()ContinueRunFlag, 单步调试快速模式QuickMode跳过Thread.Sleep和 UI 更新, 纯粹追求执行速度前缀跳转模块名以开头时跳过所有逻辑处理直接跳转, 不参与计时pIndex 嵌套计数器循环索引栈eIndexStack保证了嵌套循环各自的计数独立文档说明: 基于 Project.cs 844行源码静态分析生成。LogicMethod区域包含约150行深度嵌套的条件/循环跳转逻辑, 是整个项目最复杂的代码段。当前版本 2026-06-10。