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

Jay*_*uzi 281 c# async-await

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

Foo foo = GetFooAsync(...).GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

这是一样的吗?

Foo foo = GetFooAsync(...).Result;
Run Code Online (Sandbox Code Playgroud)

It'*_*ie. 156

差不多.虽然有一个小的区别:如果Task失败,GetResult()只会抛出直接造成的异常,同时Task.Result会抛出一个AggregateException.但是,使用其中任何一个时有async什么意义呢?100x更好的选择是使用await.

此外,你不打算使用GetResult().它仅适用于编译器,不适合您.但如果你不想讨厌AggregateException,请使用它.

  • @JayBazuzi如果您的单元测试框架支持异步单元测试,我认为大多数框架的最新版本都没有. (27认同)
  • @JayBazuzi:MSTest,xUnit和NUnit都支持`async Task`单元测试,并且已经有一段时间了. (14认同)
  • 推回100倍 - 如果您正在调整旧代码并使用等待需要重写,那么使用等待会减少1000倍. (14认同)
  • @AlexZhukovskiy:[我不同意](http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html). (13认同)
  • "100倍更好的选择是使用等待".这样做的一个例子是单元测试方法.你必须在某个时候同步,对吗? (11认同)
  • `100x更好的选择是使用await.我讨厌这样的陈述,如果我能在它面前击打`await`我会.但是,当我试图获取异步代码以对抗非异步代码时,例如Xamarin经常发生的事情*很多*我最终不得不使用像`ContinueWith`这样的东西来使它不能死锁UI.*编辑:我知道这是旧的,但这并没有减轻我的挫折感,找到答案,说明没有其他选择,你不能只使用`await`.* (8认同)
  • 推荐使用 `.GetAwaiter.Result` 代替 `.Result`。您的回答具有误导性...查看 [Microsoft 博客](https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main/) (4认同)
  • @StephenCleary很好,有时它适合.但是,例如,当我在WCF服务(或任何其他框架)的构造函数中运行一些初始化时,我不能说框架`嘿,使用我的酷静态方法而不是构造函数). (3认同)
  • 你好?构造函数?我们不能在那里使用异步。 (3认同)
  • @Ozkan - 我对细节的痴迷才刚刚开始,实际上博客上写着`.GetAwaiter().GetResult()` (2认同)

Nit*_*wal 116

Task.GetAwaiter().GetResult()优先于Task.Wait并且Task.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.Wait和Task.Result被设计破坏了吗?他们为什么不被淘汰? (4认同)
  • 所以基本上`Task.GetAwaiter().GetResult()` 等价于`await task`。我假设当方法不能用`async`(例如构造函数)标记时使用第一个选项。那是对的吗?如果是,则它与最佳答案发生冲突@It'sNotALie (3认同)
  • @OlegI:Task.GetAwaiter()。GetResult()更类似于Task.Wait和Task.Result(因为这三个都将同步阻止并可能出现死锁),但是Task.GetAwaiter ().GetResult()`具有等待任务的异常传播行为。 (3认同)
  • “不要阻塞异步代码”这句话真的让我很沮丧。我正在将较旧的控制台应用程序从 .NET 3.x 和 4.5 转换为 .NET 6.0 以获取 LTS 支持。我正在对存储库和服务类使用异步调用,但某些原始代码并未针对异步操作进行设置,因此我必须阻止异步调用。由于我处于 STA 线程中,如果异步调用使用 GetAwaiter().GetResult(),这是否可以接受?请记住,这不是理论,而是一个实际示例,因此我希望得到一个实际用例的答案。 (3认同)
  • @DanielLorenz:请参见以下引号:“使用ConfigureAwait(false)避免死锁是一种危险的做法。在阻塞代码调用的所有方法(包括所有第三个方法)的可传递关闭中,您必须为每次等待使用ConfigureAwait(false)。 -和第二方代码。使用ConfigureAwait(false)避免死锁充其量只是一种破解。)更好的解决方案是“不要阻止异步代码”。-https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html (2认同)
  • @ScottyMacDev IMO,你回答了你自己的问题。有时您就是无法遵循“最佳实践”。只是要注意其中的陷阱。考虑使用ContinueWith()而不是“GetAwaiter().GetResult()”的延续。如果您需要捕获上下文,可以将“TaskScheduler.FromCurrentSynchronizationContext()”设置为参数。 (2认同)

小智 66

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

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

  • 这里引用的来源是引用其他人而没有引用的人.考虑上下文:我可以看到很多人在阅读之后盲目地使用GetAwaiter().GetResult()到处都是. (18认同)
  • 如果两个任务以异常结束,那么你将在这个场景中失去第二个任务:Task.WhenAll(task1,task2).GetAwaiter().GetResult();`. (9认同)
  • 所以我们不应该使用它? (2认同)

Nur*_*mir 30

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

GetFooAsync(...).Result;
Run Code Online (Sandbox Code Playgroud)

GetFooAsync(...).GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)

仍然有效.

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

  • @wojciech_rak在您的代码中,您正在将`Result`与`GetIntAsync()`一起使用,它返回`Task &lt;int&gt;`而不只是`Task`。我建议您再次阅读我的答案。 (2认同)
  • 你是对的,起初我明白你的回答是你不能在返回“Task”的函数内使用“GetFooAsync(...).Result”。现在这是有道理的,因为 C# 中没有 void 属性(“Task.Result”是一个属性),但您当然可以调用 void 方法。 (2认同)
  • “Task”没有返回值,因此我们预计“.Result”是一个错误。事实上,“task.GetAwaiter().GetResult()”仍然有效,这是违反直觉的,值得强调一下。 (2认同)

Ogg*_*las 15

如前所述,如果可以使用await。如果您需要像您提到的那样同步运行代码.GetAwaiter().GetResult().Result或者.Wait()像许多评论/回答中所说的那样存在死锁的风险。由于我们大多数人都喜欢oneliners,因此您可以将其用于.Net 4.5<

通过异步方法获取值:

var result = Task.Run(() => asyncGetValue()).Result;
Run Code Online (Sandbox Code Playgroud)

同步调用异步方法

Task.Run(() => asyncMethod()).Wait();
Run Code Online (Sandbox Code Playgroud)

使用不会出现死锁问题Task.Run

资源:

/sf/answers/2270082741/

  • 为什么它可以防止死锁?我意识到 `Task.Run` 将工作卸载到 `ThreadPool`,但我们仍在等待 _this_ 线程完成该工作。 (2认同)

Ram*_*yev 8

我检查了TaskOfResult.csTaskOfResult.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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public TaskAwaiter GetAwaiter()
{
    return new TaskAwaiter(this);
}
Run Code Online (Sandbox Code Playgroud)

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

public TResult GetResult()
{
    TaskAwaiter.ValidateEnd(m_task);
    return m_task.Result;
}
Run Code Online (Sandbox Code Playgroud)

它是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的结论:

可以看出GetResult()是调用TaskAwaiter.ValidateEnd(...),所以Task.Result不一样GetAwaiter.GetResult()

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

我在C# 7 in a Nutshell (Joseph Albahari & Ben Albahari) 书中的第 582 页读到了这篇文章

如果先行任务出错,则在继续代码调用 时重新抛出异常 awaiter.GetResult()GetResult我们可以简单地访问前件的 Result 属性,而不是调用 。调用的好处 GetResult是,如果前件出现故障,则直接抛出异常而不用包裹在 中 AggregateException,从而允许更简单、更清晰的 catch 块。

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