结论
SetOfflineAsync的 UI 卡顿主要不是async失效,而是其中仍存在同步等待和同步硬件调用。设备断电后,底层 Socket 读取或重连会等待超时,调用链最终阻塞 WPF Dispatcher。
已定位的问题
SelectMode在进入关闭模式前,先同步调用:StationControlViewModel.Mode.cs
StopAutomaticRealtimeAcquisition();该方法内部使用:
StationControlViewModel.Realtime.cs
StopAutomaticRealtimeAcquisitionAsync().GetAwaiter().GetResult();实时线程如果正执行
_loadUnit.ReadStatus()或其它同步 Socket 读取,取消任务不能立即中断底层同步读取,UI 会一直等待。SetOfflineAsync内部又重复同步停止一次:StationControlViewModel.Safety.cs
这会导致同一次关闭流程最多发生两次等待。
RFID 工装次数写回虽然方法名是
FlushToRfidAsync,但底层读取是同步执行的:RFIDController.cs
ReadWithRetryAsync中直接执行read(),如果 RFID 已断电,这段同步读取会在 UI 线程上等待超时。该操作发生在 PLC 连接判断之前。PLC 判断只检查对象是否为空:
StationControlViewModel.Safety.cs
断电后对象可能仍然存在,但
IsConnected == false。此时仍会尝试执行三条 PLC 指令:(plc.WriteNonForceLock("关闭"),plc.WriteRunLightOff("关闭"),plc.WriteAllTowerLightsOff("关闭"))元组表达式会无条件执行三次写入,即使第一条已经失败,也会继续等待后续通讯超时。
HasConnectedHardware只判断驱动对象是否存在,不代表设备当前在线:StationControlViewModel.Safety.cs
推荐优化计划
先消除 UI 线程同步等待
将以下调用全部改为异步等待:
awaitStopAutomaticRealtimeAcquisitionAsync();重点修改:
SelectMode中的关闭模式分支;SetOfflineAsync内部;StartAutomaticRealtimeAcquisition等其它 UI 调用路径。
最好删除或限制使用同步包装方法:
StopAutomaticRealtimeAcquisition()这样即使后台实时线程还需要等待一次设备超时,UI 线程也不会被阻塞。
RFID 写回前先判断连接状态
关闭模式时:
if(devices?.RFID2?.Connected==true){// 执行工装次数写回}else{// 跳过写卡,保留本地 PendingCount,并记录日志}同时将 RFID 写回放入后台线程:
awaitTask.Run(()=>_fixtureUsageService.FlushToRfidAsync(devices,"切换关闭模式",CancellationToken.None));关闭模式下工装次数写回属于追溯动作,不应阻塞安全离线。设备断电时保留本地待写次数,后续设备恢复后再补写。
PLC 使用
IsConnected预检查PLCController?plc=devices?.PLCController;if(plc==null||!plc.IsConnected){errors.Add("辅控PLC未连接,跳过安全输出写入。");}else{// 执行 PLC 安全关闭}注意:PLC 未连接时仍要继续执行:
ApplyOfflineState()- 自动化 Close 通知
DisconnectStationDevicesAsync()
不能因为 PLC 断线直接
return,否则旧 Socket 和后台轮询可能无法释放。PLC 指令改为失败即停
保留原有顺序,但不要使用无条件执行的元组:
intunlock=plc.WriteNonForceLock("关闭");if(unlock!=0||!plc.IsConnected)return;intrunLight=plc.WriteRunLightOff("关闭");if(runLight!=0||!plc.IsConnected)return;inttower=plc.WriteAllTowerLightsOff("关闭");这样 PLC 断电时只产生一次有效失败,不会连续等待三次通讯超时。
主控板同样增加连接判断
devices.MainBoard != null只能说明对象存在,还应判断:devices.MainBoard.IsConnected主控板断电时跳过测试使能关闭和高压关闭写入,记录安全停机失败,继续离线和释放连接。
ExecuteMainBoardAsync已经在线程池执行,不需要再嵌套Task.Run。区分“设备对象存在”和“设备已连接”
建议保留两个概念:
HasHardwareObjects:决定是否需要释放旧设备对象;ConnectionSnapshot:决定是否可以发送硬件命令。
不能用“对象存在”作为“设备在线”的判断。
释放动作继续保持后台执行
当前
DisconnectStationDevicesAsync已通过Task.Run执行设备释放,且会先解绑上下文、停止 PLC 轮询再释放 Socket,这部分方向是正确的,应保留。
建议执行顺序
- 修改
SelectMode和SetOfflineAsync,移除.GetAwaiter().GetResult()。 - RFID 写回增加
Connected判断,并放入后台线程。 - PLC、主控板增加
IsConnected判断。 - PLC 三条指令改为失败即停。
- 保留离线状态更新和设备释放,即使硬件写入失败也必须完成。
- 使用 PLC、主控板、RFID 全部断电场景验证 UI 响应、日志和连接释放。