通八洲科技

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

日期:2025-12-29 00:00 / 作者:月夜之吻
必须 await 才能捕获 async 方法中的异常,否则异常被吞掉或触发 UnobservedTaskException;try/catch 需直接包围 await 表达式,async void 是异常黑洞,仅限顶层事件处理器。

async 方法里不加 await 会丢失异常

调用 async 方法但没用 await,比如直接写 DoSomethingAsync();,异常不会抛到当前上下文,而是被吞掉或触发 TaskScheduler.UnobservedTaskException(.NET 5+ 默认静默丢弃)。这会让错误难以定位。

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,每个都应单独考虑异常路径。不能假设前一个成功,后一个就一定安全。

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