严格状态机已经能拒绝部分非法操作,但距离“工站唯一状态来源”和工业级架构还有以下问题。
高优先级问题
- 状态仍然不是唯一来源
当前同时存在多套状态:
StationWorkflowStateMachineStationRuntimeIsBatchOpen_isWorkflowRunning_isResetting_isBatchClosingRunModeEnvironmentData.StationStatusEnvironmentData.ElectricalTestStateAutomaticPipelineActivity
例如正式测试开始时:
StationControlViewModel.cs 设置 _isWorkflowRunning = true,同时调用状态机进入 WorkflowRunning;但测试结束时只设置 _isWorkflowRunning = false,并没有同步更新状态机。
可能出现:
_isWorkflowRunning = false
WorkflowState = WorkflowRunning
因此状态机和实际业务状态可能不一致。
建议后续由状态机统一维护生命周期状态,StationRuntime 只维护运行数据,不能再让业务直接修改多个布尔字段。
- 命令准入不是原子操作
当前命令大致是:
检查状态
进入业务方法
业务方法再次进入邮箱
再次检查状态
执行具体业务
虽然已经增加了邮箱内二次校验,但“状态检查”和“业务占用”仍不是一个原子过程。
例如:
StartLot 检查通过
Stop 命令改变状态
StartLot 继续进入业务
目前只能依赖后续业务再次判断,不能从架构上保证命令已经成功占用工站。
建议增加:
CommandContext
ExpectedState
StateVersion
OperationId
命令必须携带状态版本,执行时使用:
当前版本未变化
且状态符合预期
才允许占用并执行
- 停止状态与实际流水线状态含义不完全一致
当前停止按钮只停止电学测试,之后仍然要继续:
吹扫
下料搬运
人工取料
但是停止流程可能很快将状态设置为:
StoppedAwaitingReset
此时实际上流水线可能仍有产品在吹扫或等待人工取料。
因此:
StoppedAwaitingReset
不能简单表示“所有生产流程都已经停止”,它实际表示:
测试已停止,流程正在收尾,等待复位
建议增加明确的状态或状态组合:
TestingStopped
Draining
WaitingForUnload
StoppedAwaitingReset
或者保留生命周期状态,同时通过 PipelineActivity 明确区分“测试停止”和“流水线收尾”。
- 状态机允许 Offline 从任意状态进入
当前迁移规则允许:
任意状态 -> Offline
但软件状态进入 Offline 不代表硬件已经停止,也不代表器件已经离开设备。
如果将来某个异常分支直接调用:
ApplyButtonState(StationButtonState.Offline);
可能出现:
界面显示离线
后台 Worker 仍在运行
PLC 仍有器件
主控板仍在测试
建议 Offline 只能由生命周期服务在完成以下动作后进入:
- 停止测试;
- 停止自动上料;
- 等待流水线退出;
- 关闭主控输出;
- PLC 复位或安全释放;
- 确认硬件状态。
中优先级问题
- StationControlViewModel 仍然承担过多业务职责
虽然已经拆分为多个 .partial.cs,但本质仍然是一个巨大类,仍包含:
- 测试流程;
- 自动上料;
- 自动化 SOT;
- EAP Host 放行;
- 复位;
- 停止;
- 结批;
- PLC 判断;
- 窗体弹窗;
- 按钮状态;
- 数据文件;
- 日志;
- 设备状态。
例如自动化方法仍在:
StationControlViewModel.Automation.cs
后续应将 HandleEapStartLotAsync、HandleEapSOTAsync 等方法迁移到:
StationAutomationService
StationEapService
StationLifecycleService
StationMaterialService
StationTestService
ViewModel 最终只保留命令转发和界面属性更新。
- Automation 和 EAP 仍共用过于靠近协议的服务
当前 EAP 网关和 Automation 服务最终都依赖:
StationAutomationService
共享底层工站业务是合理的,但不应让 EAP 直接依赖名称带有 Automation 语义的服务。
当前容易造成误解:
EAP -> StationAutomationService
Automation -> StationAutomationService
建议调整为:
Automation -> StationWorkflowFacade
EAP -> StationWorkflowFacade
协议层分别独立:
AutomationTcpProtocolAdapter
AutomationCommandRouter
StationAutomationService
SecsGemService
EapCommandMapper
StationEapService
二者只在领域业务层汇合,不共用协议语义。
- 命令名称仍使用字符串映射
当前通过字符串判断:
"StartLot"
"SOTReady"
"SOT"
"EQPUnload"
存在风险:
- 拼写错误时无法被状态机拦截;
- 新增命令容易忘记加入映射;
- 未识别命令可能绕过状态准入;
- 重命名命令时编译器不会提示。
建议将 EnqueueCommandAsync 改为接收强类型:
StationWorkflowCommand.StartLot
StationWorkflowCommand.Sot
StationWorkflowCommand.EndLot
不要让核心业务依赖字符串命令名。
- 硬件资源锁还不是完整的工站级互斥
当前 PLC、主控板、负载板各自有独立锁。这可以防止同一设备同时访问,但不能保证一个完整工艺动作的原子性。
例如:
先写主控板
再写 PLC
再写负载板
如果中途插入复位命令,可能出现:
主控板已配置
PLC 尚未配置
复位已经开始
建议增加两种锁:
设备级锁:保护单个设备通讯
工站级事务锁:保护跨多个设备的完整动作
涉及多个设备的初始化、复位、测试启动、结批动作,必须持有工站级锁。
- PLC 快照和实时读取仍未完全统一
当前项目中同时存在:
ReadSignal()
ReadSignalFresh()
AutomaticRealtimeSnapshot
PLC 直接读取
这会造成不同业务看到不同时间点的数据。
例如:
实时服务看到允许上料 = 1
SOTReady 直接读取到允许上料 = 0
建议统一为:
PlcSnapshot
SnapshotSequence
CapturedAt
IsCommunicationValid
SignalValues
业务只消费带时间戳的快照;只有必须确认最新硬件状态的安全动作,才使用明确标注的 Fresh Read。
- 状态变化事件缺少异常隔离
TryTransitionStrict 更新状态后触发:
StateChanged?.Invoke(...)
如果后续订阅者抛出异常,可能出现:
状态已经改变
调用方却收到异常
按钮未刷新
业务认为转移失败
建议:
- 状态变更事件内部独立捕获异常;
- 状态提交和 UI 通知分离;
- UI 通知失败不能回滚状态;
- 日志记录订阅者异常。
- 运行态字段仍可能组成矛盾状态
当前多个字段分别使用 Volatile 读写,例如:
Resetting
StopRequested
WorkflowRunning
BatchClosing
AutomaticInitializationCompleted
单字段读写是线程安全的,但多个字段组合不是原子的,可能读到:
Resetting = false
StopRequested = true
BatchClosing = false
WorkflowRunning = true
建议改成不可变快照整体替换:
StationRuntimeSnapshot
所有运行态变化通过一个原子方法更新,避免读到半更新状态。
- 操作日志数据库写入方式会阻塞业务
当前每次记录都同步打开数据库、建表、插入,并且数据库异常被直接吞掉。
风险:
- PLC 等待线程可能被数据库阻塞;
- 高速阶段变化产生大量 SQLite 操作;
- 数据库异常时不容易被发现;
- 日志事实可能丢失。
建议:
业务线程 -> 内存日志队列
后台日志 Worker -> SQLite
同时保留文本日志作为故障兜底,并记录数据库写入失败次数。
建议优化顺序
- 增加状态机转移矩阵单元测试;
- 将
StationWorkflowStateMachine移到Services.Workflow; - 用
StationRuntimeSnapshot替换多个独立布尔字段; - 所有控制命令改成强类型命令;
- 将状态检查和命令占用合并为原子操作;
- 增加工站级事务锁;
- 统一 PLC 快照和 Fresh Read 规则;
- 将停止、收尾、复位状态重新定义清楚;
- 继续迁移 ViewModel 中的测试、生命周期和协议业务;
- 最后完善操作日志队列、恢复机制和统一释放流程。
目前最需要优先处理的是前三项:状态唯一来源、命令原子占用、停止状态语义。这三项如果不先解决,继续拆分文件只能降低代码长度,不能彻底解决状态错乱和并发竞态。

443

被折叠的 条评论
为什么被折叠?



