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

来源:这里教程网 时间:2026-02-21 17:35:50 作者:

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

相关推荐