调用Task.WhenAll后获取异步任务返回结果的正确方式是什么?
结论
在已经await Task.WhenAll(params System.Threading.Tasks.Task[] tasks) 返回的任务后,优先选择再次await原始任务的方式获取结果,不推荐使用Task.Result属性。
核心原因
- 异常处理更简洁直观
如果任务执行故障,
Task.Result会将原始异常包装在AggregateException中抛出,需要手动拆包才能获取真实报错信息;而await会直接抛出任务的原始异常,无需额外解包操作,异常捕获逻辑更简单易读。就算已经通过Task.WhenAll捕获过全局异常,单独获取单个任务报错时,await的写法优势也十分明显。
- 完全规避死锁风险
当前场景下任务已经执行完成,
Task.Result不会触发同步阻塞,但代码会持续迭代修改:如果后续维护时移除了前面的await Task.WhenAll逻辑,Task.Result会立刻阻塞当前线程,在存在同步上下文的环境(比如WPF、ASP.NET Framework)中极大概率触发死锁;而await的写法哪怕删除了前置的WhenAll await,也始终是异步非阻塞的,不会引入死锁隐患,代码可维护性更强。
- 兼容性更好
如果后续将普通
Task<T>替换为性能更好的ValueTask<T>,await的写法无需做任何修改;而ValueTask<T>没有公开的Result属性,所有用到Task.Result的地方都要全部改写,适配成本更高。
- 无额外性能开销
对于已经执行完成的任务,await操作会被编译器直接优化为返回结果,不会产生任何异步调度的额外开销,性能和读取
Task.Result几乎一致,不需要担心写法更优会带来性能损失。
代码示例
Task<TResult1> task1= ... Task<TResult2> task2= ... Task<TResult3> task3= ... await Task.WhenAll(task1, task2, task3) // 推荐写法 var b = await task1; // 返回 TResult1 // 不推荐写法 var a = task1.Result; // 返回 TResult1
内容的提问来源于stack exchange,提问作者Autiarii

