async 方法里不加 await 会丢失异常
调用
async方法但没用
await,比如直接写
DoSomethingAsync();,异常不会抛到当前上下文,而是被吞掉或触发
TaskScheduler.UnobservedTaskException(.NET 5+ 默认静默丢弃)。这会让错误难以定位。 必须
await才能捕获异常,或显式调用
.Wait()/
.Result(不推荐,可能死锁) 若需“火后即忘”,至少用
_= DoSomethingAsync().ContinueWith(t => { if (t.IsFaulted) Log(t.Exception); });
单元测试中容易漏掉这个点:没 await的异步调用,
Assert.Throws会失败
try/catch 包裹 await 表达式才有效
try/catch必须直接围住
await调用,而不是整个方法体或
async声明处。因为
async方法返回的是
Task,异常实际封装在该
Task中,只有
await才会解包并重抛。
public async Task ProcessAsync()
{
try
{
// ✅ 正确:await 在 try 内,异常可被捕获
await File.ReadAllTextAsync("missing.txt");
}
catch (FileNotFoundException ex)
{
Log(ex);
}
}
<p>public async Task ProcessAsyncBad()
{
// ❌ 错误:异常发生在 await 之后,但 try 没包住它
var task = File.ReadAllTextAsync("missing.txt");
try
{
await task; // 异常仍在此抛出,但 try 已结束?不,这行还在 try 内 —— 关键是别把 await 和 try 拆开
}
catch (FileNotFoundException) { ... }
}多个 await 连续调用时,每个都可能抛异常
一个
async方法里有多个
await,每个都应单独考虑异常路径。不能假设前一个成功,后一个就一定安全。
await后的代码属于“延续(continuation)”,一旦前面的
Task出错,后续语句根本不执行 常见误写:
var data = await GetDataAsync(); var result = await ProcessAsync(data);—— 若
ProcessAsync抛异常,
data已获取但无法清理,需考虑
using或
try/finally若需“尽力而为”,可用
Task.WhenAll(...).ContinueWith(...)分离错误处理逻辑,但语义更重
async void 是异常黑洞,仅限事件处理器
async void方法中的异常无法被常规
try/catch捕获,会直接崩掉当前同步上下文(如 UI 线程),且无法通过
Task观察。只允许用于真正顶层事件,例如 WPF 的
Button.Click。
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
try
{
await LoadDataAsync(); // ✅ 可以,但异常会触发 AppDomain.UnhandledException
}
catch (Exception) { /* ❌ 不会被执行 */ }
}
<p>private async Task OnButtonClickGood(object sender, RoutedEventArgs e)
{
try
{
await LoadDataAsync(); // ✅ 推荐:返回 Task,调用方可 await + catch
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}复杂点在于:异步流中异常不是“同步抛出”,而是延迟到
await解包时才浮现;很多调试器默认不中断未处理的异步异常,得手动开启“Common Language Runtime Exceptions > Thrown”才能看到。
