【问题标题】:Using the result of an async method使用异步方法的结果
【发布时间】:2016-10-02 17:47:32
【问题描述】:

我有一个带有签名的简单异步方法:

public async Task<bool> AllowAccessAsync(string authenticatedUserToken)

调用此方法时,将其结果分配给局部变量时,我似乎有两个选择:

bool response = null;
// option 1
await this.transportService.AllowAccessAsync(authenticatedUserToken).ContinueWith(task => response = task.Result);
// option 2
response = await this.transportService.AllowAccessAsync(authenticatedUserToken);

第一个使用延续委托分配给局部变量,第二个将结果直接分配给变量。

这些结果是否相同?这两种方法有什么优势吗?有没有更好的方法来做到这一点?

【问题讨论】:

    标签: c# .net asynchronous async-await task-parallel-library


    【解决方案1】:

    这些结果是否相同?

    编辑:

    @Servy 正确指出,因为 ContinueWith 只是结果的投影。这意味着这两个操作在语义上是等效的,但是在例外情况下它们的行为会有所不同。

    这两种方法有什么优势吗?有没有更好的方法来做到这一点?

    编辑 2:

    以下备注一般与async-awaitContinueWith 的使用有关。具体来看这两个示例,因为它们都使用async-await,肯定使用后者,因为两者都包含状态机生成,但后者将传播AggregateException,以防发生异常。

    async-await 产生状态机 的开销最小,而ContinueWith 没有。另一方面,使用async-await 让您在实际异步的同时“感觉”同步,并为您节省ContinueWith 的冗长。我肯定会选择async-await,不过我建议你研究使用它的正确路径,因为可能会有意想不到的可怜。

    【讨论】:

    • 这个答案的前半部分是错误的。他没有创建Task&lt;Task&gt;,他只是创建了Task&lt;bool&gt; 并等待它。
    • @Servy 下半场也是。这两个选项都创建了一个状态机。我刚刚添加了一个不同的答案:stackoverflow.com/a/30711892/885318
    • @i3arnon 第二部分在两个例子中都没有讨论状态机,它讨论了ContinueWith vs async-await
    • @YuvalItzchakov 但async-await 用于两个选项。
    【解决方案2】:

    通过将async/await 与任务并行库的ContinueWith 一起使用,您会不必要地混合模式。有很多理由不这样做,除非你别无选择,尤其是 async/awaitSynchronizationContext is not preserved by the default implementation of ContinueWith

    总之,选项2是正确的。

    【讨论】:

      【解决方案3】:

      这些结果是否相同?

      是的,这两个选项最终会将相同的结果设置为response。唯一的区别发生在有异常时。在第一个选项中,抛出的异常将是实际异常的 AggregateException 包装器,而在第二个选项中,它将是实际异常。

      这两种方法有什么优势吗?

      以这种方式使用ContinueWith 绝对没有优势。第二个选项的优点是具有更好的异常处理和更简单的读写。

      有没有更好的方法来做到这一点?

      不是真的,这就是你使用 async-await 的方式。


      正如 Servy 所评论的,这不是 ContinueWith 的用途。 ContinueWith 等效项是将方法的其余部分放入延续中。所以不要这样:

      public async Task FooAsync()
      {
          var response = await this.transportService.AllowAccessAsync(authenticatedUserToken);
          Console.WriteLine(response);
      }
      

      你会这样做:

      public Task FooAsync()
      {
          return this.transportService.AllowAccessAsync(authenticatedUserToken).
              ContinueWith(task => Console.WriteLine(task.GetAwaiter().GetResult()));
      }
      

      这确实有一些性能优势,因为它不需要 async-await 状态机,但要正确处理非常复杂。

      【讨论】:

        【解决方案4】:

        这更多地与编码风格有关。

        当您有一个大型工作流程并且可能希望在执行承诺时分配不同的延续时,第一种样式可能会很有用。但是它依赖于闭包。我不推荐这种用法,因为它并不总是可预测的。 ContinueWith 应该在您创建工作流时使用,并且每个步骤仅依赖于前面的步骤,并且不与外部范围通信,除非通过交付产生最终结果的任务(然后您将等待)。

        当您只对结果感兴趣时,第二个很有用。

        此外,ContinueWith 允许您指定 TaskScheduler,因为您的应用程序默认提供的可能不是您想要的。

        更多关于TaskScheduler的信息在这里: http://blog.stephencleary.com/2015/01/a-tour-of-task-part-7-continuations.html

        【讨论】:

          猜你喜欢
          • 2019-03-19
          • 1970-01-01
          • 1970-01-01
          • 2018-09-01
          • 2016-06-21
          • 2014-01-19
          • 1970-01-01
          • 2016-05-14
          • 1970-01-01
          相关资源
          最近更新 更多