c# 异步方法的异常处理 c# async await try catch

必须 await 才能捕获 async 方法中的异常,否则异常被吞掉或触发 UnobservedTaskException;try/catch 需直接包围 await 表达式,async void 是异常黑洞,仅限顶层事件处理器。

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);
    }
}

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 已获取但无法清理,需考虑 usingtry/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) { /* ❌ 不会被执行 */ }
}

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”才能看到。