我最近在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。代码确实:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这与
相同吗?Foo foo = GetFooAsync(...).Result;
答案 0 :(得分:134)
差不多。但有一点不同:如果Task
失败,GetResult()
将直接抛出异常,而Task.Result
会抛出AggregateException
。但是,当它是async
时,使用其中任何一个是什么意思? 100x更好的选择是使用await
。
此外,您不打算使用GetResult()
。它仅适用于编译器,不适合您。但如果您不想要烦人的AggregateException
,请使用它。
答案 1 :(得分:82)
Task.GetAwaiter().GetResult()
优先于Task.Wait
和Task.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
答案 2 :(得分:64)
https://github.com/aspnet/Security/issues/59
“最后一句话:您应该避免使用
Task.Result
和Task.Wait
尽可能多,因为他们总是将内部异常封装在一个AggregateException
并用通用的(一个或多个)替换消息 发生了更多错误),这使得调试更加困难。即使是 不应该经常使用同步版本,你应该强烈使用 考虑使用Task.GetAwaiter().GetResult()
代替。“
答案 3 :(得分:28)
另一个区别是当async
函数只返回Task
而不是Task<T>
时,您就无法使用
GetFooAsync(...).Result;
而
GetFooAsync(...).GetAwaiter().GetResult();
仍然有效。
我知道问题中的示例代码是针对案例Task<T>
的,但问题一般是问。
答案 4 :(得分:7)
我检查了 TaskOfResult.cs
(Source code of TaskOfResult.cs):
如果 Task
未完成,Task.Result
将调用 Task.Wait()
中的 getter
方法。
public TResult Result
{
get
{
// If the result has not been calculated yet, wait for it.
if (!IsCompleted)
{
// We call NOCTD for two reasons:
// 1. If the task runs on another thread, then we definitely need to notify that thread-slipping is required.
// 2. If the task runs inline but takes some time to complete, it will suffer ThreadAbort with possible state corruption.
// - it is best to prevent this unless the user explicitly asks to view the value with thread-slipping enabled.
//#if !PFX_LEGACY_3_5
// Debugger.NotifyOfCrossThreadDependency();
//#endif
Wait();
}
// Throw an exception if appropriate.
ThrowIfExceptional(!m_resultWasSet);
// We shouldn't be here if the result has not been set.
Contract.Assert(m_resultWasSet, "Task<T>.Result getter: Expected result to have been set.");
return m_result;
}
internal set
{
Contract.Assert(m_valueSelector == null, "Task<T>.Result_set: m_valueSelector != null");
if (!TrySetResult(value))
{
throw new InvalidOperationException(Strings.TaskT_TransitionToFinal_AlreadyCompleted);
}
}
}
如果我们调用 GetAwaiter
的 Task
方法,Task
将包裹 TaskAwaiter<TResult>
(Source code of GetAwaiter()), (Source code of TaskAwaiter) :
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
如果我们调用GetResult()
的{{1}}方法,它会调用TaskAwaiter<TResult>
属性,那个Task.Result
会调用Task.Result
的{{1}}方法(Source code of GetResult()):
Wait()
是Task
(Source code of ValidateEnd(Task task))的源代码:
public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}
这是我的结论:
可以看出 ValidateEnd(Task task)
正在调用 internal static void ValidateEnd(Task task)
{
if (task.Status != TaskStatus.RanToCompletion)
HandleNonSuccess(task);
}
private static void HandleNonSuccess(Task task)
{
if (!task.IsCompleted)
{
try { task.Wait(); }
catch { }
}
if (task.Status != TaskStatus.RanToCompletion)
{
ThrowForNonSuccess(task);
}
}
,因此 GetResult()
与 TaskAwaiter.ValidateEnd(...)
不同。
我认为 Task.Result
是比 GetAwaiter.GetResult()
更好的选择,因为它不包含异常。
我在 C# 7 in a Nutshell(Joseph Albahari 和 Ben Albahari)一书中的第 582 页读到了这篇文章
<块引用>如果一个先行任务发生故障,当
继续代码调用 GetAwaiter().GetResult()
。而不是打电话
.Result
,我们可以简单地访问
前因。调用 awaiter.GetResult()
的好处是,如果
前因错误,异常直接抛出,不被
包裹在 GetResult
中,允许更简单和更干净
捕获块。
答案 5 :(得分:4)
如前所述,如果可以使用await
。如果您需要像提到.GetAwaiter().GetResult()
一样同步运行代码,则.Result
或.Wait()
可能会陷入僵局,就像许多人在评论/答案中所说的那样。由于我们大多数人都喜欢oneliners,因此您可以将其用于.Net 4.5<
通过异步方法获取值:
var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法
Task.Run(() => asyncMethod()).Wait();
由于使用Task.Run
,不会发生死锁问题。
来源:
答案 6 :(得分:0)
如果任务发生故障,则继续执行时会重新引发异常 代码调用awaiter.GetResult()。而不是调用GetResult,我们 可以简单地访问任务的Result属性。好处 调用GetResult的原因是,如果任务失败,则异常是 直接抛出而不包装在AggregateException中,从而允许 以获得更简单,更清洁的捕获块。
对于非常规任务,GetResult()的返回值是无效的。它有用 然后,功能仅是抛出异常。
source:坚果中的c#7.0