Task.Run使用async/await与不使用时Task.WaitAll的行为差异分析
Task.Run中async/await有无的行为差异分析
一、两种写法下GetAllData都会阻塞到所有任务完成
没错,不管是带async/await的Task.Run(async () => await SomeFunction(...)),还是不带的Task.Run(() => SomeFunction(...)),GetAllData都会阻塞直至所有任务完成。原因很直接:
- 两种写法里,
Task.Run都会返回一个代表异步操作生命周期的Task对象。 Task.WaitAll的作用就是阻塞当前线程,直到传入的所有Task全部结束(不管是成功完成、超时还是抛出异常)。- 哪怕是不带
async/await的写法,Task.Run(() => SomeFunction(...))返回的是嵌套的Task<Task>,Task.WaitAll会自动解包这种嵌套结构,等待内部的异步流程彻底完成,所以最终阻塞效果和带async/await的版本完全一致。
二、移除async/await后的执行流程变化(针对CPU密集型的GetData1/GetData2)
先明确核心逻辑:TimeoutAfter方法里已经通过Task.Run(action)把CPU密集型的GetData方法放到线程池执行了,外层的Task.Run只是多了一层调度。两种写法的流程差异主要体现在SomeFunction的执行阶段:
不带async/await的写法(Task.Run(() => SomeFunction(...)))
Task.Run会从线程池拿一个线程,同步执行SomeFunction的前置同步代码(也就是//do some work那部分)。- 当执行到
await action.TimeoutAfter(...)时,SomeFunction会返回一个未完成的Task,此时外层的Task.Run会把这个未完成的Task作为自己的返回值(也就是Task<Task>)。 Task.WaitAll会自动等待这个嵌套Task的内部Task完成——也就是等待TimeoutAfter里的GetData执行+超时判断的整个流程结束。
带async/await的写法(Task.Run(async () => await SomeFunction(...)))
Task.Run同样从线程池拿线程执行async lambda,当碰到await SomeFunction(...)时,lambda会被挂起,直到SomeFunction的异步流程完成。- 这里的
async/await本质上只是语法糖,让代码逻辑更直观,和不带的写法在性能、最终执行结果上没有差异——因为Task.Run本身就会处理返回的Task对象。
唯一的关键区别:异常处理
如果SomeFunction或TimeoutAfter抛出异常:
- 带
async/await的写法会把异常直接包装到返回的Task中,堆栈信息更清晰。 - 不带的写法里,外层
Task.Run返回的Task<Task>不会直接捕获内部Task的异常,但Task.WaitAll在等待时会检测到内部异常并抛出,所以对GetAllData的阻塞行为没有影响,只是调试异常时,嵌套Task的堆栈会稍微复杂一点。
另外要注意:因为GetData是CPU密集型,TimeoutAfter里的Task.Run已经把它放到线程池了,所以外层Task.Run的有无async/await,都不会改变GetData的执行线程——它始终在线程池上跑。
内容的提问来源于stack exchange,提问作者Flack
相关产品推荐
相关产品推荐

