完全异步?

17

我正在尝试理解新的异步/等待模式,我有一个问题找不到答案,即如果我打算从其他异步函数调用这些方法,我是否应该使用async修饰我的方法,或者只在适当的时候返回Tasks?

换句话说,A、B或C类中哪个最好,为什么?

class A<T>
{
      public async Task<T> foo1() //Should be consumed
      {
          return await foo2();
      }

      public async Task<T> foo2() //Could be consumed
      {
          return await foo3();
      }

      private async Task<T> foo3() //Private
      {
          return await Task.Run(...);
      }
}

class B<T>
{
      public async Task<T> foo1() //Should be consumed
      {
          return await foo2();
      }

      public async Task<T> foo2() //Could be consumed
      {
          return await foo3();
      }

      private Task<T> foo3() //Private
      {
          return Task.Run(...);
      }
}

class C<T>
{
      public async Task<T> foo1() //Should be consumed
      {
          return await foo2();
      }

      public Task<T> foo2() //Could be consumed
      {
          return foo3();
      }

      private Task<T> foo3() //Private
      {
          return Task.Run(...);
      }
}

在装饰方法方面,过度装饰似乎是多余的,因此我自然倾向于使用 C,但同时,如果不使用 await 关键字,与 Task<T> 一起工作会感到有些尴尬。


1
顺便提一下,“async all the way down”这个短语通常意味着你不应该在异步代码上阻塞。即使您不使用“async”关键字,此代码仍然是完全异步的。(顺便说一句,我觉得我最初是听到Lucian Wischik使用这个短语的,但现在我找不到Stephen Toub的博客文章之前的用法)。 - Stephen Cleary
这就是为什么我希望不需要添加async关键字的要求。我理解“可能会在某个地方破坏现有代码”,但它会导致像这样的问题/困惑,与能够选择使用yield return进行另一个编译器重写相比 :) - James Manning
2个回答

10

这两个版本的效果基本相同,唯一的区别就是在这里使用 await 会有一些性能惩罚(因为必须设置状态机并且可能会使用 continuation)。

所以,这涉及到一个权衡:你想要你的方法在稍微降低可读性的情况下更有效吗?还是你愿意为了可读性而牺牲性能?

通常,我会建议你首先考虑可读性,只有在分析表明值得时才关注性能。但在这种情况下,我认为可读性的提高很小,所以我可能不会使用 await

还请注意,你的类 C 还不够完善:即使是 foo1() 也不需要 await


5
尽管我更强烈地认为它们都不应该是“async”。在我看来,这并不会使代码可读性变差...也就是说,如果读者理解这是一个可等待的方法返回类型,而不是方法本身 - Stephen Cleary

3
在签名中的async是为了允许编译器创建状态机重写所包含的代码,这对于在一般情况下实现await语义是必要的。
您的示例恰好是不需要该重写的特殊情况:异步操作是方法内发生的最后一件事。这种方法在.NET4.0中已经可以实现并且有效。这种兼容性可能是在不需要时避免使用async的原因之一。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接