我最近正在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。代码可以:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这是一样的吗
Foo foo = GetFooAsync(...).Result;
我最近正在阅读一些使用大量异步方法的代码,但有时需要同步执行它们。代码可以:
Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
这是一样的吗
Foo foo = GetFooAsync(...).Result;
差不多。但是有一个小区别:如果Task
失败,GetResult()
只会抛出直接导致的异常,而Task.Result
会抛出AggregateException
. 但是,当它是时使用其中任何一个有什么意义async
?100 倍更好的选择是使用await
.
另外,您不应该使用GetResult()
. 它仅供编译器使用,不适合您。但是,如果您不想烦人AggregateException
,请使用它。
Task.GetAwaiter().GetResult()
优于Task.Wait
并且Task.Result
因为它传播异常而不是将它们包装在AggregateException
. 但是,所有三种方法都可能导致死锁和线程池饥饿问题。他们都应该避免而赞成async/await
。
下面的引用解释了为什么Task.Wait
并且Task.Result
不简单地包含Task.GetAwaiter().GetResult()
(由于“非常高的兼容性条”)的异常传播行为。
正如我之前提到的,我们有一个非常高的兼容性标准,因此我们避免了重大更改。因此,
Task.Wait
保留其始终包装的原始行为。Task.Wait
但是,您可能会发现自己处于某些高级情况下,您希望行为类似于AggregateException
. 为此,您可以直接定位任务的等待者。当您编写“<code>await task;”时,编译器会将其转换为方法的使用,该Task.GetAwaiter()
方法返回一个具有GetResult()
方法的实例。在故障任务上使用时,GetResult()
将传播原始异常(这是“<code>await task;”获取其行为的方式)。因此,如果您想直接调用此传播逻辑,则可以使用“<code>task.GetAwaiter().GetResult()”。
https://blogs.msdn.microsoft.com/pfxteam/2011/09/28/task-exception-handling-in-net-4-5/
“<code>GetResult”实际上意味着“检查任务是否有错误”</p>
一般来说,我尽量避免同步阻塞异步任务。但是,在少数情况下我确实违反了该准则。在那些罕见的情况下,我首选的方法是
GetAwaiter().GetResult()
因为它保留了任务异常,而不是将它们包装在AggregateException
.
http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html
https://github.com/aspnet/Security/issues/59
“最后一点:你应该尽可能避免使用
Task.Result
和Task.Wait
,因为它们总是将内部异常封装在一个中AggregateException
,并用一个通用的(发生一个或多个错误)替换消息,这使得调试更加困难。即使同步版本应该'不经常使用,你应该强烈考虑Task.GetAwaiter().GetResult()
改为使用。”
另一个区别是当async
函数返回Task
而不是Task<T>
then 你不能使用
GetFooAsync(...).Result;
然而
GetFooAsync(...).GetAwaiter().GetResult();
仍然有效。
我知道问题中的示例代码是针对这种情况的Task<T>
,但是通常会问这个问题。
如前所述,如果您可以使用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
更新:
如果调用线程来自线程池,可能会导致死锁。会发生以下情况:一个新任务被排到队列的末尾,并且最终将执行该任务的线程池线程被阻塞,直到该任务被执行。
资源:
https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d
我检查了TaskOfResult.cs
(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>
(GetAwaiter()的源代码),(TaskAwaiter的源代码):
public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}
如果我们调用GetResult()
的方法TaskAwaiter<TResult>
,它会调用Task.Result
属性,这Task.Result
将调用Wait()
方法Task
(GetResult()的源代码):
public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}
它是( ValidateEnd(Task task) 的源代码)的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()
的。
我认为这 GetAwaiter().GetResult()
是一个更好的选择,而不是.Result
因为它不包含异常。
我在 Nutshell (Joseph Albahari & Ben Albahari) 书中C# 7 的第 582 页读到了这篇文章
如果前面的任务出错,则在继续代码调用时重新抛出异常
awaiter.GetResult()
。GetResult
我们可以简单地访问前件的 Result 属性,而不是调用 。调用的好处GetResult
是,如果前件出错,则直接抛出异常而不用包裹在 中AggregateException
,从而允许更简单、更清晰的 catch 块。
如果任务出错,则在继续代码调用 awaiter.GetResult() 时重新抛出异常。我们可以简单地访问任务的 Result 属性,而不是调用 GetResult。调用 GetResult 的好处是,如果任务出错,则直接抛出异常,而不用包裹在 AggregateException 中,从而允许更简单、更清晰的 catch 块。
对于非泛型任务,GetResult() 有一个 void 返回值。那么它的有用功能就是重新抛出异常。
来源:C# 7.0 简而言之