☰
`SetOfflineAsync` 的 UI 卡顿主要不是 `async` 失效,而是其中仍存在同步等待和同步硬件调用
2026/10/14 21:16:14 网站建设 项目流程

结论

SetOfflineAsync的 UI 卡顿主要不是async失效,而是其中仍存在同步等待和同步硬件调用。设备断电后,底层 Socket 读取或重连会等待超时,调用链最终阻塞 WPF Dispatcher。

已定位的问题

  1. SelectMode在进入关闭模式前,先同步调用:

    StationControlViewModel.Mode.cs

    StopAutomaticRealtimeAcquisition();

    该方法内部使用:

    StationControlViewModel.Realtime.cs

    StopAutomaticRealtimeAcquisitionAsync().GetAwaiter().GetResult();

    实时线程如果正执行_loadUnit.ReadStatus()或其它同步 Socket 读取,取消任务不能立即中断底层同步读取,UI 会一直等待。

  2. SetOfflineAsync内部又重复同步停止一次:

    StationControlViewModel.Safety.cs

    这会导致同一次关闭流程最多发生两次等待。

  3. RFID 工装次数写回虽然方法名是FlushToRfidAsync,但底层读取是同步执行的:

    RFIDController.cs

    ReadWithRetryAsync中直接执行read(),如果 RFID 已断电,这段同步读取会在 UI 线程上等待超时。该操作发生在 PLC 连接判断之前。

  4. PLC 判断只检查对象是否为空:

    StationControlViewModel.Safety.cs

    断电后对象可能仍然存在,但IsConnected == false。此时仍会尝试执行三条 PLC 指令:

    (plc.WriteNonForceLock("关闭"),plc.WriteRunLightOff("关闭"),plc.WriteAllTowerLightsOff("关闭"))

    元组表达式会无条件执行三次写入,即使第一条已经失败,也会继续等待后续通讯超时。

  5. HasConnectedHardware只判断驱动对象是否存在,不代表设备当前在线:

    StationControlViewModel.Safety.cs

推荐优化计划

  1. 先消除 UI 线程同步等待

    将以下调用全部改为异步等待:

    awaitStopAutomaticRealtimeAcquisitionAsync();

    重点修改:

    • SelectMode中的关闭模式分支;
    • SetOfflineAsync内部;
    • StartAutomaticRealtimeAcquisition等其它 UI 调用路径。

    最好删除或限制使用同步包装方法:

    StopAutomaticRealtimeAcquisition()

    这样即使后台实时线程还需要等待一次设备超时,UI 线程也不会被阻塞。

  2. RFID 写回前先判断连接状态

    关闭模式时:

    if(devices?.RFID2?.Connected==true){// 执行工装次数写回}else{// 跳过写卡,保留本地 PendingCount,并记录日志}

    同时将 RFID 写回放入后台线程:

    awaitTask.Run(()=>_fixtureUsageService.FlushToRfidAsync(devices,"切换关闭模式",CancellationToken.None));

    关闭模式下工装次数写回属于追溯动作,不应阻塞安全离线。设备断电时保留本地待写次数,后续设备恢复后再补写。

  3. PLC 使用IsConnected预检查

    PLCController?plc=devices?.PLCController;if(plc==null||!plc.IsConnected){errors.Add("辅控PLC未连接,跳过安全输出写入。");}else{// 执行 PLC 安全关闭}

    注意:PLC 未连接时仍要继续执行:

    • ApplyOfflineState()
    • 自动化 Close 通知
    • DisconnectStationDevicesAsync()

    不能因为 PLC 断线直接return,否则旧 Socket 和后台轮询可能无法释放。

  4. PLC 指令改为失败即停

    保留原有顺序,但不要使用无条件执行的元组:

    intunlock=plc.WriteNonForceLock("关闭");if(unlock!=0||!plc.IsConnected)return;intrunLight=plc.WriteRunLightOff("关闭");if(runLight!=0||!plc.IsConnected)return;inttower=plc.WriteAllTowerLightsOff("关闭");

    这样 PLC 断电时只产生一次有效失败,不会连续等待三次通讯超时。

  5. 主控板同样增加连接判断

    devices.MainBoard != null只能说明对象存在,还应判断:

    devices.MainBoard.IsConnected

    主控板断电时跳过测试使能关闭和高压关闭写入,记录安全停机失败,继续离线和释放连接。ExecuteMainBoardAsync已经在线程池执行,不需要再嵌套Task.Run。

  6. 区分“设备对象存在”和“设备已连接”

    建议保留两个概念:

    • HasHardwareObjects:决定是否需要释放旧设备对象;
    • ConnectionSnapshot:决定是否可以发送硬件命令。

    不能用“对象存在”作为“设备在线”的判断。

  7. 释放动作继续保持后台执行

    当前DisconnectStationDevicesAsync已通过Task.Run执行设备释放,且会先解绑上下文、停止 PLC 轮询再释放 Socket,这部分方向是正确的,应保留。

建议执行顺序

  1. 修改SelectMode和SetOfflineAsync,移除.GetAwaiter().GetResult()。
  2. RFID 写回增加Connected判断,并放入后台线程。
  3. PLC、主控板增加IsConnected判断。
  4. PLC 三条指令改为失败即停。
  5. 保留离线状态更新和设备释放,即使硬件写入失败也必须完成。
  6. 使用 PLC、主控板、RFID 全部断电场景验证 UI 响应、日志和连接释放。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询