发布于2026-07-11 阅读(0)
扫一扫,手机访问
C# 异步编程中异常处理的坑,说起来不算多,但几乎每个项目都会踩上几个。尤其是当你从同步代码切换到 async/await 时,原来熟悉的 try/catch 逻辑突然就不那么可靠了——异常不会消失,只是换了一种诡异的藏身方式。下面这几点,是真正让人头疼的地方,也是每一个写异步代码的人必须刻进脑子里的原则。
必须await才能捕获异步异常,否则异常会挂起在Task中或触发UnobservedTaskException;async void是异常黑洞,仅限 UI 事件处理器使用。

异步方法返回 Task 或 Task 时,如果你只是调用了它但没写 await,异常不会立刻抛出,也不会被外围的 try/catch 逮住——它就这么“挂”在 Task 对象上,像个休眠的冲击波。直到你某天访问 .Result、调用 .Wait() 或者真正 await 它,才会轰地炸开,而此时堆栈信息早就丢了,调试起来极其痛苦。
var task = DoSomethingAsync();(没 await,也没后续处理)await DoSomethingAsync();,或者至少显式检查 task.Exceptionawait(比如在事件处理器里启动后台任务),请用 task.ContinueWith(t => { if (t.IsFaulted) LogError(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted)async void 方法无法被 await,它内部抛出的异常会直接变成未处理异常,触发 AppDomain.UnhandledException 或 TaskScheduler.UnobservedTaskException,几乎无从捕获和定位。这个坑比前面的更隐蔽,因为编译器不会报错,只有 Roslyn 分析器(比如 CA2008)会警告。
button_Click),而且必须确保内部所有异常都被 try/catch 兜底或记录下来async void,统一用 async Task用 Task.WhenAll 并行执行多个异步操作时,只要其中一个失败,返回的 Task 就会把所有的子异常打包成一个 AggregateException。如果你直接 catch(Exception),只能抓到最外层的包装,真实的错误信息被藏在了里面。
catch(AggregateException ex),然后遍历 ex.InnerExceptionsawait Task.WhenAll(tasks).ConfigureAwait(false); —— 在 .NET Core 2.1+ 下,await 默认会直接透出异常,避免多一层包装Task.WhenAny 不会聚合异常,失败任务的异常仍然保留在其自身的 Task 实例中ConfigureAwait(false) 不影响异常传播路径。有人误以为加了 .ConfigureAwait(false) 会让异常“丢”掉,其实它只控制延续(continuation)是否回到原始上下文,对异常是否抛出、如何封装完全没有影响。异常仍然会按照 await 点正常向上冒泡,try/catch 的位置不变。这个配置的真正作用是避免死锁(比如在 ASP.NET 同步上下文中调用 .Result)以及减少上下文切换的开销。类库代码建议无脑加,应用层看需求;但加不加都不改变异常捕获的逻辑。
异常真正难缠的地方不在于“怎么 catch”,而在于“谁该负责 catch”——上游调用方常默认下游已经兜底,下游又觉得异常应该由调用方处理,结果两边都没接住。务必在模块边界明确异常责任,比如仓储层吞掉网络超时并转成 RepositoryUna vailableException,让业务层决定是重试还是降级。把责任边界画清楚,比任何异常捕获技巧都管用。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8