".Wait()和.GetAwaiter().GetResult()之间有什么区别?"

128
我的方法返回一个Task。我想等待它完成。我应该使用.Wait()还是.GetAwaiter().GetResult()?它们之间有什么区别?
2个回答

151

两者都会同步等待操作结果(如果可能,应该避免使用它们)。

区别主要在于异常处理。使用Wait方法时,异常堆栈跟踪未被修改,并表示异常发生时的实际堆栈,因此,如果您的代码在线程池线程上运行,则堆栈将类似于:

ThreadPoolThread.RunTask
YourCode.SomeWork

另一方面,.GetAwaiter().GetResult()将重构堆栈跟踪以考虑所有异步上下文,忽略某些代码部分在UI线程上执行,某些在ThreadPool线程上执行,而有些则是简单的异步I/O。因此,您的堆栈跟踪将反映对您的代码类似于同步的步骤:

TheSyncMethodThatWaitsForTheAsyncMethod
YourCode.SomeAsyncMethod
SomeAsync
YourCode.SomeWork

这往往会使异常堆栈跟踪变得更加有用,可以说是至关重要的。您可以看到 YourCode.SomeWork 在您应用程序的上下文中被调用,而不是“物理运行方式”。

这种工作原理的示例在参考源代码中有展示(当然,这不是合同文件)。


7
Task.GetAwaiter()返回一个TaskAwaiter,但是TaskAwaiter.GetResult()的文档建议:“此API支持产品基础结构,不建议直接从代码中使用。”你有何评论?答:虽然 TaskAwaiter.GetResult() 是 Task 异步操作的关键方法之一,但它主要是为了支持编译器生成异步代码而设计的。在正常情况下,应该在 await 表达式中使用 TaskAwaiter,而不是直接调用它的 GetResult() 方法。因此,最好遵循文档中的建议,避免直接使用 GetResult() 方法。 - DavidRR
27
整个 TaskAwaiter 是一个实现细节。另一方面,可等待/等待器机制已经有文档并且是使用鸭子类型的——GetAwaiter 相当于 GetEnumerator 对于 foreach 或者 Dispose 对于 using。这些都在C#规范中定义了,无论使用哪种特定的等待器 - 注意,Task.GetAwaiter 是 "旨在供编译器使用而不是应用程序代码使用"。但关键是预期使用的是 await,而不是 Wait() 或者 GetAwaiter().GetResult()——但如果需要的话,GetResult 可以给你更好的堆栈信息。 - Luaan
2
@Luaan usingDispose()不使用鸭子类型。只有在实现IDisposable时才有效。而foreachGetEnumerator()以及awaitGetAwaiter()则使用鸭子类型。 - codingdave
@codingdave 哦,你说得对,在编译C#代码时有类型检查。本地代码实际上并没有进行虚拟调用,但这与鸭子类型并不完全相同。 - Luaan

0

尽力避免在异步任务上同步阻塞。

在那些罕见的例外情况下,GetAwaiter().GetResult()将保留任务异常。

如果使用Wait会抛出AggregateException异常。

参考@stephen-cleary的博客文章'a-tour-of-task-part-6-results'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接