2026-09-10 综合博客:异步异常 + 排查核实思维

2026-09-10 综合博客:异步异常 + 排查核实思维

今天的内容分两大块:语法 (异步方法的异常去哪了)和思维(排查「查不到/超时」、核实「功能到底实现没」)。代码全部示意化(脱敏),语法用真名。


上半场 · 语法:异步方法的异常去哪了

同样一个会抛异常的异步方法,调用姿势不同,异常的去向完全不同:

复制代码
调用一个 async 方法,异常抛出来走哪?

① 不 await(发完不管,返回的 Task 被丢弃)
   └─→ 后台 UnobservedTaskException(GC 回收时才触发,没人接 = 静默)

② async void / UI 线程上同步抛
   └─→ UI 线程 DispatcherUnhandledException(e.Handled 这个开关在这才有效)

类比:报警器响了,声音传给谁,取决于「接线接到哪」。接后台值班室(没人),就是 ①------响了个寂寞;接前台保安亭(有人守),就是 ②。

今天那个「该弹窗却没弹」的坑,本质就是异常走了 ①,而兜底接在 ②------线没接对,当然没反应

坑 1:不 await(fire-and-forget)

概念 :调用异步方法但不 await、也不接返回值,方法里抛异常就回不到调用方。

类比:托人送快递不留回执------快递半路丢了,没人通知你。

csharp 复制代码
// ❌ 不 await:异常装进被丢弃的 Task,回不来
void 点按钮() { 触发动作(); }   // 里面抛异常,这里接不住

// ✅ await + try-catch:把异常拉回来
async Task 点按钮()
{
    try { await 触发动作(); }
    catch (Exception e) { 弹窗(e.Message); }
}

误区:别以为「没 await 就是走 UI 线程报错」------方向反了,没 await 恰恰是抛到后台去。

坑 2:ConfigureAwait(false) 切到线程池

概念 :默认 await 结束后会切回原上下文(如 UI 线程);加 .ConfigureAwait(false) 就是「别切回来」,后续代码可能落线程池。

类比 :UI 线程和线程池是两拨人------UI 线程只有一条,专门管界面(画控件、接点击);线程池是一堆后台线程,Task/await 默认在里面排队干活,不管界面。

csharp 复制代码
await 干活();                        // 默认:切回 UI 线程继续
await 干活().ConfigureAwait(false);  // 不切回,可能落线程池

ConfigureAwait(false) 之后直接弹窗,代码可能已落在后台线程池,一弹窗就「跨线程访问界面」报错。修法:弹窗外面套 Dispatcher.Invoke 强制切回 UI 线程。

坑 3:空 catch 静默失败 + catch 三件套

概念catch (Exception) { } 空 catch------把异常吞掉,不记日志不提示,程序照跑,用户无感知。这是「静默失败」最典型的形态。

类比:火警响了,但报警线被剪断------火在烧,没人听见。

正确姿势:catch 三件套(缺一不可)

csharp 复制代码
catch (Exception e)
{
    记录日志(e);                       // ① 留痕,事后能查
    Application.Current.Dispatcher.Invoke(() =>
        弹窗(e.Message));              // ② 切 UI 线程 ③ 让用户看见
}

只弹窗不记日志 → 事后查不到;只记日志不弹窗 → 现场没人发现。


下半场 · 排查与核实思维

一、「查不到数据」其实是「被过滤了」

------别急着归因「断了」。

二、超时是「0 帧」不是「慢」

现象:任务报超时。

真根因:不是设备慢,是「0 帧」。上一个位置正常收满,下一个位置触发时 0 帧------因为每次扫完都把数据流停掉再重启,下次触发时设备还没就绪,错过了触发。

类比:水龙头每次用完都关掉,下次开要等水重新流出来;动作太快就接不到一滴水。不是水流得慢,是「根本没接到」。

关键区分:「0」和「慢」是两码事。0 帧 = 没触发(流没就绪);慢 = 真卡(帧慢慢到)。看日志先分清是「0」还是「慢」,别把「没触发」当「性能差」。

三、核实「功能实现了没」要追到调用处

今天核实功能时踩了两次「下结论太快」的坑,教训都指向同一件事:

  1. 看到「过滤条件写死排除某类」→ 别急着说「没实现」,要追它读的是不是配置(可能配置默认关,所以现场看起来像没有)。
  2. 看到「只写库」→ 别急着说「没有独立文件」,要追提交之后有没有自动导出。

类比:看到墙上开关是关的,别以为「没装开关」------要查它后面到底接没接线、线通到哪。开关默认关 ≠ 没这功能。

一句话 :核实「实现了没」,必须追到调用处 / 配置来源,不能停在过滤条件或方法内部。

四、消息驱动解耦(概念)

概念:按钮做完事,只「广播」一条消息,别的模块自己订阅、自己响应。按钮代码完全不知道响应方存在。

类比:广播------播音员不知道谁在听,谁听谁行动。好处是「非侵入」:加新功能不用改按钮代码,新模块自己挂到广播上就行。


语法附录

  • async void vs async Task:Task 能 await/catch;void 不能,异常强抛回 UI 线程。
  • await vs 不 await :不 await = fire-and-forget,异常进 UnobservedTaskException
  • ConfigureAwait(false):后续代码不切回原上下文,可能落线程池。
  • DispatcherUnhandledException :UI 线程异常处理器,e.Handled=true 标记已处理、静默。
  • UnobservedTaskException:被丢弃 Task 的异常,GC 回收才触发,时机不靠谱。
  • Dispatcher.Invoke:从后台线程池切回 UI 线程(弹窗前包一层)。
  • catch {}:静默失败,修法 = catch 三件套(记日志 + 切 UI 线程 + 弹窗)。

结尾

今天一整天,绕来绕去就两个词:「没反应」和「查不到」

  • 语法侧:异步异常走错路、没人接 → 静默。记住两条路 + catch 三件套。
  • 排查侧:「查不到」先分清是「断了」还是「被过滤」;「超时」先分清是「0」还是「慢」;「核实」要追到调用处,别停在表面。

一句话:碰到「没反应却没报错」,先问「异常走哪条路、有没有人接」;碰到「查不到」,先问「查错表没、被过滤没」------别急着归因,先分清表象下的两码事。

相关推荐
geovindu6 天前
CSharp: Command Pattern
开发语言·后端·c#·.net·.netcore·命令模式·行为模式
Java后端的Ai之路7 天前
21、Python - 命令模式
开发语言·人工智能·python·命令模式·外观模式
Flutter OH9 天前
Flutter OH 卡死冻屏问题定位指南
flutter·命令模式
AI人工智能+电脑小能手16 天前
大白话说Java设计模式-34-命令模式(业务实战篇)
java·spring·设计模式·命令模式·异步任务·撤销重做·事务封装
云深处@18 天前
【C++设计模式】命令模式
c++·设计模式·命令模式
林浩杨_1 个月前
linux搜索命令(grep+sed)
linux·命令模式
若衹如初見1 个月前
一步一步学习使用LiveBindings() LiveBindings图像绑定与自定义绑定方法()
学习·命令模式
ttod_qzstudio2 个月前
【软考设计模式】命令模式:请求封装与调用接收解耦的精讲
设计模式·命令模式
其实防守也摸鱼2 个月前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式