Task.Result是否与.GetAwaiter.GetResult()相同?


328

我最近在阅读一些使用很多异步方法的代码,但是有时需要同步执行它们。该代码可以:

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();

这和

Foo foo = GetFooAsync(...).Result;

8
来自文档GetResult:“此类型及其成员旨在供编译器使用。” 其他人不应该使用它。
支出者2013年

32
这称为“异步同步”,除非您知道任务的实现方式,否则这是一个非常糟糕的主意。在很多情况下它可能立即死锁(例如,在MVC中为async/ await方法)
Marc Gravell


14
在现实世界中,我们有构造函数,我们需要实现“无等待”接口,并且到处都有异步方法。我很乐意使用一些行之有效的东西,而不必怀疑为什么它是“危险的”,“不被使用”或“不惜一切代价避免”的。每一次我不得不为异步而烦恼时,都会感到头疼。
拉里

Answers:


173

差不多了 但是有一个小的区别:如果Task失败,GetResult()将仅引发直接导致的异常,而Task.Result将引发AggregateException。但是,在使用它们时有async什么意义呢?最好使用100倍await

另外,您也不打算使用GetResult()。它只供编译器使用,不适合您。但是,如果您不想烦人AggregateException,请使用它。


27
@JayBazuzi如果您的单元测试框架支持异步单元测试,那不是,我认为大多数框架的最新版本都支持。
svick

15
@JayBazuzi:MSTest,xUnit和NUnit都支持async Task单元测试,并且已经有一段时间了。
Stephen Cleary

18
推迟100倍-如果您要调整旧代码,而使用await则需要重写,因此使用await的情况要差1000倍。

13
@AlexZhukovskiy:我不同意
史蒂芬·克利西

15
The 100x better option is to use await.我讨厌这样的陈述,如果我能await在它前面打耳光,我会。但是,当我试图让异步代码来对抗像什么经常发生在我身上的非异步代码工作了很多在Xamarin,我最终不得不用的东西一样ContinueWith不少,以使其不死锁的UI。编辑:我知道这很老了,但是这并不能减轻我的沮丧感,因为对于不能仅仅使用的情况,没有其他选择的答案await
Thomas F.

147

Task.GetAwaiter().GetResult()优先于Task.WaitTask.Result因为它传播异常而不是将异常包装在AggregateException。但是,这三种方法都可能导致死锁和线程池不足的问题。应该避免他们都赞成async/await

下面的引用解释了为什么Task.Wait并且Task.Result不仅仅包含Task.GetAwaiter().GetResult()(由于“非常高的兼容性”)的异常传播行为。

正如我之前提到的,我们有一个很高的兼容性标准,因此我们避免了改动。因此,Task.Wait保留了始终包装的原始行为。但是,您可能会发现自己处在某些高级情况下,这些情况下您想要的行为类似于所采用的同步阻塞Task.Wait,但是您希望将原始异常展开而不是传播,而不是将其封装在中AggregateException。为此,您可以直接定位任务的等待者。当您编写“ await task;”时,编译器Task.GetAwaiter()会将其转换为方法的用法,这将返回具有GetResult()方法的实例。当用于有故障的任务时,GetResult()将传播原始异常(这是“ await task;” 如何获得其行为)。因此,您可以使用“task.GetAwaiter().GetResult()如果您想直接调用此传播逻辑。

https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/

GetResult”实际上表示“检查任务是否有错误”

通常,我会尽最大努力避免异步阻塞异步任务。但是,在少数情况下,我确实违反了该准则。在那些罕见的情况下,我的首选方法是GetAwaiter().GetResult()因为它保留了任务异常,而不是将它们包装在中AggregateException

http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html


3
所以基本上Task.GetAwaiter().GetResult()等同于await task。我假设在无法用方法标记方法async(例如构造函数)时使用第一个选项。那是对的吗?如果是,那么它就会与最高答案冲突@ It'sNotALie
OlegI 18-10-11

5
@OlegI:Task.GetAwaiter().GetResult()Task.Wait和更等效Task.Result(因为这三个都将同步阻塞并可能产生死锁),但是Task.GetAwaiter().GetResult()具有await任务的异常传播行为。
Nitin Agarwal '18

在这种情况下,使用(Task).ConfigureAwait(false).GetAwaiter()。GetResult();可以避免死锁 ?
丹尼尔·洛伦兹

3
@DanielLorenz:请参见以下引号:“使用ConfigureAwait(false)避免死锁是一种危险的做法。在阻塞代码调用的所有方法(包括所有第三个方法)的可传递关闭中,您必须为每次等待使用ConfigureAwait(false)。 -和第二方代码。使用ConfigureAwait(false)避免死锁充其量只是一种破解。)更好的解决方案是“不要阻止异步代码”。- blog.stephencleary.com/2012/07/dont-block-on-async-code.html
尼廷·阿加瓦尔

4
我不明白 Task.Wait和Task.Result被设计破坏了吗?他们为什么不被淘汰?
osexpert

69

https://github.com/aspnet/Security/issues/59

“最后一句话:您应该避免使用Task.Result并且Task.Wait尽可能地避免它们总是将内部异常封装在中, AggregateException并用通用的消息(发生一个或多个错误)替换消息,这会使调试更加困难。即使同步版本不应该不会经常使用,您应该强烈考虑Task.GetAwaiter().GetResult()改为使用。”


20
这里引用的源是有人引用别人的,没有引用。考虑上下文:阅读本文后,我到处都能看到很多人盲目使用GetAwaiter()。GetResult()。
杰克·乌克丽雅

2
所以我们不应该使用它吗?
tofutim '17

11
如果有两个任务以异常结束,则在这种情况下您将失去第二个任务Task.WhenAll(task1, task2).GetAwaiter().GetResult();
Monsignor


33

另一个区别是当async函数返回Task而不是时,Task<T>您不能使用

GetFooAsync(...).Result;

鉴于

GetFooAsync(...).GetAwaiter().GetResult();

仍然有效。

我知道问题中的示例代码适合这种情况Task<T>,但是问题通常被问到。


1
这不是真的。看看我使用这种结构的小提琴:dotnetfiddle.net/B4ewH8
wojciech_rak,

3
@wojciech_rak在您的代码中,您正在使用ResultGetIntAsync()而返回的结果Task<int>不只是Task。我建议您再次阅读我的答案。
Nuri Tasdemir

1
您是对的,起初我理解您的回答是,您不能GetFooAsync(...).Result 在return的函数内部Task。现在这很有意义,因为C#中没有void属性(Task.Result是一个属性),但是您当然可以调用void方法。
wojciech_rak

22

如前所述,如果可以使用await。如果您需要像您提到的那样同步运行代码.GetAwaiter().GetResult().Result或者.Wait()像许多评论/回答中所说的那样存在死锁的风险。由于我们大多数人都喜欢oneliners,因此您可以将其用于.Net 4.5<

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;

同步调用异步方法

Task.Run(() => asyncMethod()).Wait();

使用不会出现死锁问题Task.Run

资源:

https://stackoverflow.com/a/32429753/3850405


1

如果任务失败,那么当连续代码调用awaiter.GetResult()时,将引发异常。无需调用GetResult,我们可以简单地访问任务的Result属性。调用GetResult的好处是,如果任务失败,则将异常直接引发而不将其包装在AggregateException中,从而允许更简单,更干净的catch块。

对于非常规任务,GetResult()的返回值是无效的。这样,它的有用功能就是仅抛出异常。

源:简而言之的c#7.0

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.