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;

Is Task.Result the same as .GetAwaiter.GetResult()?

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

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

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

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

如果我们调用TaskAwaiter<TResult>GetResult()方法,它将调用Task.Result属性,即Task.Result将调用TaskWait()方法(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.ResultGetAwaiter.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