Is Task.Result the same as .GetAwaiter.GetResult()?
本文关键字:GetResult GetAwaiter as Task Result the same Is | 更新日期: 2023-09-27 18:19:40
我最近读到一些代码,这些代码使用了很多异步方法,但有时需要同步执行它们。代码是这样做的:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这和一样吗
Foo foo = GetFooAsync(...).Result;
Task.GetAwaiter().GetResult()
优于Task.Wait
和Task.Result
,因为它传播异常而不是将它们封装在AggregateException
中。但是,这三种方法都可能导致死锁和线程池饥饿问题。它们都应该避免,以利于async/await
。
下面的引用解释了为什么Task.Wait
和Task.Result
不简单地包含Task.GetAwaiter().GetResult()
的异常传播行为(由于"非常高的兼容性条")。
正如我之前提到的,我们有一个非常高的兼容性条,因此我们避免了破坏更改。因此,
Task.Wait
保留了其始终包裹的原始行为。然而,您可能会发现自己处于一些高级情况,您希望行为类似于Task.Wait
所使用的同步阻塞,但希望原始异常展开传播,而不是封装在AggregateException
中。为了实现这一点,您可以直接针对Task的awaiter。当您编写"await task;
"时,编译器将其转换为Task.GetAwaiter()
方法的使用,该方法返回一个具有GetResult()
方法的实例。当用于出现故障的任务时,GetResult()
将传播原始异常(这就是"await task;
"获取其行为的方式)。因此,如果您想直接调用此传播逻辑,则可以使用"task.GetAwaiter().GetResult()
"。
https://devblogs.microsoft.com/pfxteam/task-exception-handling-in-net-4-5/
"
GetResult
"实际上意味着"检查任务是否有错误"一般来说,我会尽量避免异步任务上的同步阻塞。然而,在少数情况下,我确实违反了该准则。在这些罕见的情况下,我的首选方法是
GetAwaiter().GetResult()
,因为它保留了任务异常,而不是将它们封装在AggregateException
中。
https://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html
EDIT:这是我13岁时写的,已经过时了。我推荐尼廷·阿加瓦尔的答案。
差不多。不过,有一个小区别:如果Task
失败,GetResult()
将直接引发异常,而Task.Result
将引发AggregateException
。然而,当它是async
时,使用其中任何一个都有什么意义?100倍更好的选择是使用await
。
此外,您不应该使用GetResult()
。它只供编译器使用,不适合您。但如果你不想要烦人的AggregateException
,就用它吧。
https://github.com/aspnet/Security/issues/59
"最后一句话:您应该避免使用
Task.Result
和Task.Wait
作为因为它们总是将内部异常封装在AggregateException
并用通用消息(one或出现更多错误),这使得调试更加困难。即使同步版本不应该经常使用,您应该考虑改用CCD_ 32。"
另一个区别是,当async
函数只返回Task
而不是Task<T>
时,则不能使用
GetFooAsync(...).Result;
而
GetFooAsync(...).GetAwaiter().GetResult();
仍然有效。
我知道问题中的示例代码是针对Task<T>
的情况,但是这个问题通常是被问到的。
如前所述,如果您可以使用await
。如果您需要像提到的.GetAwaiter().GetResult()
那样同步运行代码,那么.Result
或.Wait()
就有死锁的风险,正如许多人在评论/回答中所说的那样。由于我们大多数人都喜欢oneliner,您可以将其用于.Net 4.5<
通过异步方法获取值:
var result = Task.Run(() => asyncGetValue()).Result;
同步调用异步方法
Task.Run(() => asyncMethod()).Wait();
由于使用了Task.Run
,不会出现死锁问题。
来源:
https://stackoverflow.com/a/32429753/3850405
更新:
如果调用线程来自线程池,则可能导致死锁。发生以下情况:一个新任务被排队到队列的末尾,最终执行该任务的线程池线程被阻塞,直到该任务被执行。
来源:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
我检查了TaskOfResult.cs
的源代码(TaskOfResult.cs的源代码):
如果Task
未完成,则Task.Result
将调用getter
中的Task.Wait()
方法。
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);
}
}
}
如果我们调用Task
的GetAwaiter
方法,Task
将封装TaskAwaiter<TResult>
(GetAwaiter()的源代码),(TaskAwaiter的源代码:
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
如果我们调用TaskAwaiter<TResult>
的GetResult()
方法,它将调用Task.Result
属性,即Task.Result
将调用Task
的Wait()
方法(GetResult()的源代码):
public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}
它是ValidateEnd(Task task)
的源代码(ValidateEnd(任务任务)的源代码):
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()
不同。
我认为GetAwaiter().GetResult()
比.Result
更好,因为前者不包装异常。
我在第582页的《坚果壳中的C#7》中读到了这篇文章(Joseph Albahari和Ben Albahari)。
如果先行任务出错,则当延续代码调用CCD_ 65。而不是打电话
GetResult
,我们可以简单地访问在先的调用GetResult
的好处是,如果先行错误,则直接抛出异常包裹在AggregateException
中,更简单、更清洁挡块。
来源:Nutshell第582页的C#
如果任务出错,则在继续时重新引发异常代码调用awaiter。GetResult()。我们不是调用GetResult,而是可以简单地访问任务的Result属性。好处调用GetResult的方法是,如果任务出错,则异常为直接抛出而不包装在AggregateException中,允许更简单、更清洁的挡块。
对于非泛型任务,GetResult()有一个void返回值。它很有用函数仅用于重新引发异常。
来源:Nutshell 中的c#7.0