Task<T>.Result是否仍会阻塞?Wait()前置调用是否必要?
一、“获取.Result前必须调用.Wait()”的说法是否仍成立?
答案是完全不成立了。这个说法其实来自.NET早期版本(大概是.NET Framework 4.0刚推出Task Parallel Library的时候),当时的文档和一些早期资料可能会有这样的建议,但实际上从.NET Framework 4.5开始,这个限制就不存在了——准确来说,其实从一开始,Result本身就会隐式等待任务完成,只是早期的误解或者文档表述问题导致了这个说法的流传。
二、这个说法的由来及变更节点
在.NET 4.0刚推出TPL的时候,部分开发者对Task的机制不熟悉,加上早期文档表述不够清晰,催生了“必须先Wait再取Result”的误解。但实际上,Task<T>.Result的设计本身就是阻塞当前线程直到任务完成,然后返回结果,它内部已经包含了等待的逻辑,和调用Wait()的效果在阻塞等待这一点上是一致的(仅异常处理有细微差别:Result会抛出任务的AggregateException,Wait()未处理异常时也会抛出,但本质都是等待任务完成)。
从.NET Framework 4.5引入async/await语法之后,官方文档和最佳实践就明确纠正了这个误解,强调Result本身就会完成等待,不需要提前调用Wait()。
三、在.Result前调用.Wait()是否还有帮助或必要?
完全没有必要,甚至属于冗余操作,原因如下:
- 两者核心逻辑都是阻塞当前线程直到任务完成,先调用
Wait()再取Result,相当于做了两次等待(任务完成后第二次等待会直接返回,但完全没必要)。 - 从性能和代码简洁性来说,直接使用
Result就足够,额外的Wait()只会增加不必要的代码开销,没有任何收益。
另外你提到的“调用.Wait()会将任务返回给调度器,待任务完成后重新进入”是混淆了await和Wait()的区别:
await是异步等待,它会释放当前线程回调度器,等任务完成后再在合适的线程上继续执行后续代码;- 而
Wait()是同步阻塞,它会让当前线程一直停在这里等待任务完成,不会释放线程回调度器——这也是为什么在UI线程或者ASP.NET上下文里调用Wait()/Result容易导致死锁的原因,而await则不会。
总结一下:现在完全不需要在取Result前调用Wait(),直接使用Result就可以完成等待并获取结果;如果是异步场景,更推荐使用await来避免阻塞和死锁问题。
内容的提问来源于stack exchange,提问作者Oren Ferrari

