是任务。结果与.GetAwaiter.GetResult()?

我最近阅读了一些使用了大量异步方法的代码,但有时需要同步执行它们。代码如下:

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

是一样的吗

Foo foo = GetFooAsync(...).Result;
216347 次浏览

编辑:这是我13岁时写的,现在已经过时了。我建议改用Nitin Agarwal的回答

差不多。但有一个小区别:如果Task失败,GetResult()将直接抛出异常,而Task.Result将抛出AggregateException。然而,当它是async时,使用它们有什么意义呢?更好的100倍选择是使用await

另外,你也不应该使用GetResult()。它仅供编译器使用,而不是供您使用。但如果你不想要恼人的AggregateException,就使用它。

https://github.com/aspnet/Security/issues/59 < a href = " https://github.com/aspnet/Security/issues/59 " > < / >

最后一句:你应该避免使用Task.ResultTask.Wait作为 尽可能多的,因为它们总是将内部异常封装在 AggregateException并将该消息替换为一个通用的消息(one或 出现了更多错误),这使得调试更加困难。即使 同步版本不应该经常使用,应该强烈使用 考虑使用Task.GetAwaiter().GetResult()代替。" < / p >

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

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

正如我之前提到的,我们有一个非常高的兼容性标准,因此我们避免了破坏性的更改。因此,Task.Wait保留其始终包装的原始行为。然而,你可能会发现自己在一些高级情况下,你想要类似于Task.Wait所采用的同步阻塞的行为,但你想要原始异常被传播而不是被封装在AggregateException中。要实现这一点,可以直接将任务的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

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

GetFooAsync(...).Result;

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

仍能工作。

我知道问题中的示例代码是针对Task<T>的情况,但是这个问题是一般的。

如前所述,如果你可以使用await。如果你需要同步运行代码,就像你提到的.GetAwaiter().GetResult()一样,.Result.Wait()有死锁的风险,就像许多人在评论/回答中说的那样。因为我们大多数人喜欢单行程序,你可以在.Net 4.5<中使用它们

通过async方法获取一个值:

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

同步调用异步方法

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

由于使用Task.Run,不会出现死锁问题。

来源:

https://stackoverflow.com/a/32429753/3850405 < a href = " https://stackoverflow.com/a/32429753/3850405 " > < / >

更新:

如果调用线程来自线程池,则可能导致死锁。发生如下情况:一个新任务排队到队列的末尾,最终将执行该任务的线程池线程被阻塞,直到该任务执行为止。

来源:

https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d

如果任务出错,则在任务继续时重新抛出异常 代码调用awaiter.GetResult()。而不是调用GetResult,我们 可以简单地访问任务的Result属性。的好处 调用GetResult的原因之一是,如果任务出错,则异常为 直接抛出,而不包装在AggregateException中,允许

对于非泛型任务,GetResult()有一个void返回值。它有用 函数仅用于重新抛出异常

源码:c# 7.0简写

我检查了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);
}
}
}

如果我们调用TaskGetAwaiter方法,Task将包装TaskAwaiter<TResult> (GetAwaiter()的源代码), (taskkawaiter的源代码):

public TaskAwaiter GetAwaiter()
{
return new TaskAwaiter(this);
}

如果我们调用TaskAwaiter<TResult>GetResult()方法,它将调用Task.Result属性,Task.Result将调用Task (GetResult()源代码)的Wait()方法:

public TResult GetResult()
{
TaskAwaiter.ValidateEnd(m_task);
return m_task.Result;
}

它是ValidateEnd(Task task) (ValidateEnd(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.ResultGetAwaiter.GetResult()不同。

我认为 GetAwaiter().GetResult()是一个更好的选择,而不是.Result,因为后者不包装异常。

我在c# 7简介的582页读到这个(Joseph Albahari &本Albahari)。

如果一个先行任务出现错误,当任务执行失败时,异常将被重新抛出 延续代码调用awaiter.GetResult()。而不是打电话 GetResult,我们可以简单地访问Result属性 先行词。调用GetResult的好处是如果 如果是前因错误,则直接抛出异常 包装在AggregateException中,允许更简单和更干净 catch块。< / p >

来源:c# 7 in a Nutshell第582页