Task.Result和.GetAwaiter.GetResult()一样吗?

时间:2013-06-24 20:28:54

标签: c# async-await

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

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

这与

相同吗?
Foo foo = GetFooAsync(...).Result;

7 个答案:

答案 0 :(得分:134)

差不多。但有一点不同:如果Task失败,GetResult()将直接抛出异常,而Task.Result会抛出AggregateException。但是,当它是async时,使用其中任何一个是什么意思? 100x更好的选择是使用await

此外,您不打算使用GetResult()。它仅适用于编译器,不适合您。但如果您不想要烦人的AggregateException,请使用它。

答案 1 :(得分:82)

Task.GetAwaiter().GetResult()优先于Task.WaitTask.Result,因为它会传播异常,而不是将其包装在AggregateException中。但是,这三种方法都可能导致死锁问题,应该避免使用async/await

以下引用解释了为什么Task.WaitTask.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.ResultTask.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);
        }
    }
}

如果我们调用 GetAwaiterTask 方法,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()

TaskSource 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 中,允许更简单和更干净 捕获块。

来源:C# 7 in a Nutshell's page 582

答案 5 :(得分:4)

如前所述,如果可以使用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

答案 6 :(得分:0)

  

如果任务发生故障,则继续执行时会重新引发异常   代码调用awaiter.GetResult()。而不是调用GetResult,我们   可以简单地访问任务的Result属性。好处   调用GetResult的原因是,如果任务失败,则异常是   直接抛出而不包装在AggregateException中,从而允许   以获得更简单,更清洁的捕获块。

     

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

source:坚果中的c#7.0