Task.Run结合异步Lambda的await等待逻辑及与StartNew的对比疑问
先看你贴的这段代码(我补全了Lambda的参数部分,正确写法应该是async () =>):
//SynchronisationContext = UI Thread await Task.Run(async () => { for (int i = 0; i < 100; i++) { var res = ComplicatedCalulation(); //耗时约1秒 await ThirdPartyLib.WriteToDatabase(res); } });
我来逐个解答你的疑问:
1. 这里的await是等待整个Lambda执行完成吗?
答案是肯定的——它会等待整个异步Lambda里的所有逻辑执行完毕,才会继续执行await后面的代码,绝不是只等Task启动就立即返回。
原因在于:Task.Run专门做了优化,当你传入一个返回Task的异步委托(也就是你的async Lambda)时,它会自动帮你解开嵌套的Task(相当于隐式调用了Unwrap方法)。最终你await的其实是Lambda内部所有异步操作完成后的那个最终Task,所以会一直等循环跑完、100次数据库写入都完成才结束。
对比你提到的Task.Factory.StartNew:它不会自动解包,如果你直接用StartNew包裹async Lambda,得到的是Task<Task>,这时候直接await只会等外层Task完成(也就是Lambda开始执行),不会等内部的异步逻辑,所以才需要额外的StartNew<Task>(...)再取.Result或者再await一次来解包。
2. StartNew的那种嵌套写法适用于Task.Run吗?
完全没必要,甚至不推荐这么做!
Task.Run本身就是为了简化异步委托的执行而设计的,它天生支持异步Lambda,并且自动处理了嵌套Task的解包工作。你现在写的await Task.Run(async () => { ... })就是最正确、最简洁的写法。
如果强行套用StartNew的写法,比如:
await Task.Run<Task>(async () => await MyAsyncMethod()).Result
这不仅画蛇添足,还可能带来问题——比如在UI线程调用.Result可能导致死锁(因为UI线程的同步上下文会等待结果,而内部的异步操作可能需要回到UI线程)。所以完全不需要这么写,用Task.Run的常规写法就足够了。
内容的提问来源于stack exchange,提问作者JKennedy

