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
相关产品推荐
相关产品推荐

