You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#异步方法中await前的异常为何不在调用时立即抛出?关于异步代码执行顺序的疑问

Async/Await执行顺序与异常抛出的疑问

示例代码

static async Task ThrowException() { throw new Exception("exception 1"); Thread.Sleep(1000); await Task.Delay(1000); throw new Exception("exception 2"); }
static async Task Sleep() { Console.WriteLine("begin sleep"); Thread.Sleep(1000); Console.WriteLine("end sleep"); Console.WriteLine("begin delay"); await Task.Delay(1000); Console.WriteLine("end delay"); }
static async Task Main(string[] args) {
    {
        Console.WriteLine("begin var task = Sleep();");
        var task = Sleep();
        Console.WriteLine("end var task = Sleep();");
        Console.WriteLine("begin await task;");
        await task;
        Console.WriteLine("end await task;");
    }
    {
        try {
            Console.WriteLine("begin var task = ThrowException();");
            var task = ThrowException();
            Console.WriteLine("end var task = ThrowException();");
            Console.WriteLine("begin await task;");
            await task;
            Console.WriteLine("end await task;");
        } catch(Exception e) {
            Console.WriteLine(e.Message);
        }
    }
}

运行结果

begin var task = Sleep();
begin sleep
end sleep
begin delay
end var task = Sleep();
begin await task;
end delay
end await task;
begin var task = ThrowException();
end var task = ThrowException();
begin await task;
exception 1

我的疑问

为什么"exception 1"出现在"begin await task;"之后,而"end sleep"却出现在"begin await task;"之前?我认为在执行await Task.Delay()之前并没有启动新线程,这段代码应该和Thread.Sleep()一样在同一线程中执行,因此我预期"exception 1"会在"end var task = ThrowException();"之前立即抛出。


解答

咱们拆解一下这两个方法的执行逻辑,就能搞懂差异的根源了:

1. Sleep方法的执行流程

当你调用var task = Sleep();时,async方法会同步执行到第一个await语句之前的所有代码:

  • 先输出begin sleep,执行同步的Thread.Sleep(1000),接着输出end sleep和begin delay;
  • 直到遇到await Task.Delay(1000),此时Task.Delay返回一个未完成的Task,Sleep方法会立即返回这个未完成的Task给调用者;
  • 所以接下来会执行end var task = Sleep();,之后当你await task时,才会等待Task.Delay完成,再执行剩下的end delay,最后结束整个方法。

这就是end sleep出现在begin await task;之前的原因——它是在调用Sleep()的同步阶段就执行完了。

2. ThrowException方法的异常处理逻辑

这里的核心规则是:返回Task的async方法,会把同步阶段抛出的异常自动包装到返回的Task中,而非立即抛出给调用者。

当你调用var task = ThrowException();时:

  • 方法执行到throw new Exception("exception 1")时,编译器会捕获这个异常,把返回的Task标记为Faulted状态,然后直接返回这个Task;
  • 所以调用代码会继续执行end var task = ThrowException();和begin await task;;
  • 只有当你await这个Faulted的Task时,.NET才会把包装在Task里的异常重新抛出,此时catch块才会捕获到它,输出exception 1。

如果是返回void的async方法,同步阶段的异常才会立即抛出,但这里返回的是Task,就会走上述包装逻辑——这也是你预期和实际结果不一样的关键原因。

内容的提问来源于stack exchange,提问作者Snowy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 06:37:27