【问题标题】:Is Task.Result the same as .GetAwaiter.GetResult()?Task.Result 是否与 .GetAwaiter.GetResult() 相同?
【发布时间】:2013-06-21 11:50:45
【问题描述】:

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

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

和这个一样

Foo foo = GetFooAsync(...).Result;

【问题讨论】:

  • 来自GetResult 的文档:“此类型及其成员旨在供编译器使用。”其他人不应该使用它。
  • 这被称为“异步同步”,除非你知道任务是如何实现的可能是一个真的坏主意。在许多情况下它会立即死锁(例如,MVC 中的 async/await 方法)
  • 在现实世界中,我们有构造函数,我们有需要实现的“无等待”接口,并且到处都有异步方法。我很乐意使用可以正常工作的东西,而不必想知道为什么它是“危险的”、“不被使用”或“不惜一切代价避免”。每次我不得不处理异步问题时都会头疼。

标签: c# async-await


【解决方案1】:

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

另外,您不应该使用GetResult()。它仅供编译器使用,不适合您。但如果你不想烦人的AggregateException,那就用它吧。

【讨论】:

  • @JayBazuzi 如果你的单元测试框架支持异步单元测试,我认为大多数框架的最新版本都支持。
  • @JayBazuzi:MSTest、xUnit 和 NUnit 都支持async Task 单元测试,而且已经有一段时间了。
  • 推迟 100 倍 - 如果您正在调整旧代码并且使用 await 需要重写,那么使用 await 会差 1000 倍。
  • @AlexZhukovskiy: I disagree.
  • The 100x better option is to use await. 我讨厌这样的陈述,如果我能在它前面拍到await 我会的。但是,当我试图让异步代码与非异步代码一起工作时,例如在 Xamarin 中经常发生在我身上的事情 a lot 时,我最终不得不在为了使它不会死锁UI。 编辑:我知道这已经过时了,但这并不能减轻我的挫败感,因为在你不能只使用 await 的情况下,找到没有其他选择的答案。
【解决方案2】:

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

【讨论】:

  • 所以基本上Task.GetAwaiter().GetResult() 等价于await task。我假设当方法不能用async(例如构造函数)标记时使用第一个选项。那是对的吗?如果是,那么它与最佳答案@It'sNotALie 冲突
  • @OlegI: Task.GetAwaiter().GetResult() 更等同于Task.WaitTask.Result(因为这三个都将同步阻塞并有可能发生死锁),但Task.GetAwaiter().GetResult() 具有异常传播行为等待任务。
  • 你不能用 (Task).ConfigureAwait(false).GetAwaiter().GetResult(); 在这种情况下避免死锁吗? ?
  • @DanielLorenz:请参阅以下引述:“使用 ConfigureAwait(false) 来避免死锁是一种危险的做法。在所有调用的方法的传递闭包中,您必须对每个 await 使用 ConfigureAwait(false)通过阻塞代码,包括所有第三方和第二方代码。使用 ConfigureAwait(false) 避免死锁充其量只是一种 hack)。...更好的解决方案是“不要阻塞异步代码”。 - blog.stephencleary.com/2012/07/dont-block-on-async-code.html
  • 我不明白。 Task.Wait 和 Task.Result 被设计破坏了吗?为什么它们没有被淘汰?
【解决方案3】:

https://github.com/aspnet/Security/issues/59

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

【讨论】:

  • 这里引用的来源是有人引用别人的,没有参考。考虑上下文:阅读本文后,我可以看到很多人在任何地方都盲目地使用 GetAwaiter().GetResult()。
  • 所以我们不应该使用它?
  • 如果两个任务以异常结束,在这种情况下您将失去第二个任务Task.WhenAll(task1, task2).GetAwaiter().GetResult();
【解决方案4】:

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

GetFooAsync(...).Result;

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

仍然有效。

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

【讨论】:

  • 这不是真的。看看我的小提琴正是使用了这个结构:dotnetfiddle.net/B4ewH8
  • @wojciech_rak 在您的代码中,您将ResultGetIntAsync() 一起使用,它返回Task<int> 而不仅仅是Task。我建议您再次阅读我的答案。
  • 你是对的,起初我明白你的回答是你不能在GetFooAsync(...).Result inside 一个返回Task 的函数。现在这是有道理的,因为 C# 中没有 void 属性(Task.Result 是一个属性),但您当然可以调用 void 方法。
  • Task 没有返回值,所以我们预计.Result 是一个错误。 task.GetAwaiter().GetResult() 仍然有效的事实是违反直觉的,值得强调一下。
【解决方案5】:

如前所述,如果您可以使用await。如果您需要像您提到的.GetAwaiter().GetResult().Result.Wait() 那样同步运行代码,那么正如许多人在 cmets/answers 中所说的那样,存在死锁的风险。由于我们大多数人都喜欢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

【讨论】:

  • 如果您投反对票,请说明原因。否则很难改进答案。
  • 为什么它可以防止死锁?我意识到Task.Run 将工作卸载到ThreadPool,但我们仍在等待 this 线程以完成该工作。
  • @Mike 只使用.Result.Wait() 的问题是,如果你阻塞了应该在任务上工作的线程,那么就没有线程来完成任务.你可以在这里阅读更多信息:medium.com/rubrikkgroup/…
【解决方案6】:

查了TaskOfResult.cs的源码(Source code of 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&lt;TResult&gt;(Source code of GetAwaiter()),(Source code of TaskAwaiter):

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

如果我们调用TaskAwaiter&lt;TResult&gt;GetResult()方法,它将调用Task.Result属性,即Task.Result将调用TaskWait()方法(Source code of GetResult()):

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

ValidateEnd(Task task)Source code of 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.ResultGetAwaiter.GetResult() 不同。

我认为GetAwaiter().GetResult() 是比.Result 更好的选择,因为它不包含异常。

我在 C# 7 in a Nutshell(Joseph Albahari 和 Ben Albahari)一书的第 582 页读到此内容

如果前面的任务出错,则在执行任务时重新抛出异常 继续代码调用 awaiter.GetResult() 。而不是打电话 GetResult ,我们可以简单地访问 Result 的属性 前因。调用GetResult 的好处是,如果 先行错误,直接抛出异常而不被 包裹在 AggregateException 中,允许更简单和更清洁 抓块。

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

【讨论】:

    【解决方案7】:

    如果任务出错,则在继续时重新抛出异常 代码调用 awaiter.GetResult()。我们没有调用 GetResult,而是 可以简单地访问任务的 Result 属性。好处 调用 GetResult 是如果任务出错,异常是 直接抛出而不被包裹在 AggregateException 中,允许 用于更简单、更干净的 catch 块。

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

    来源:简而言之 c# 7.0

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-10
      • 1970-01-01
      • 1970-01-01
      • 2010-09-13
      • 2011-01-28
      • 1970-01-01
      • 2021-08-17
      • 2019-11-12
      相关资源
      最近更新 更多