【问题标题】:Nested awaits in C# impact on performanceC# 中的嵌套等待对性能的影响
【发布时间】:2023-03-30 11:21:02
【问题描述】:

我在 Api 中公开了一个方法,预计每秒有 500 次点击。

我已将所有 API 设置为异步响应。 每个请求中可能有多个数据库调用,应该是异步的还是同步的。

我的观点是嵌套等待让我们说 3 个级别。 在这种情况下,它会提高性能还是上下文切换会严重影响性能。 我已经搜索了足够多,但我越来越困惑。 我应该遵循一些经验法则。

同样在底部,数据库调用是同步调用,我不能像已经写的那样做很多事情。

【问题讨论】:

  • 只有 1 条经验法则...基准,因为任何代码行都可能是一个问题,而您还没有提供任何代码,谁知道呢,还有为什么您的 dB 调用会同步.. .

标签: c# performance asp.net-web-api async-await task


【解决方案1】:

awaits 的数量通常并不重要。如果您发现性能不合适,您可以做一些事情(例如,使用ValueTask<T> 而不是Task<T>)。

但是,最大的性能问题在这里:

在底部,数据库调用是同步调用

如果低级调用是同步的,那么您可能会遇到可伸缩性问题。如果当前代码正在执行“异步超过同步”(例如,Task.RunTask.Factory.StartNew),那么这将是一个问题。要么将代码更改为“一路异步”(即,使 db 调用异步)并接受需要完成的额外工作,要么将其更改为一路同步并接受同步解决方案的有限可扩展性.

【讨论】:

  • 如果数据库调用的性质是一劳永逸。就像只是更新并且不从中返回任何内容一样,在这种情况下,同步数据库调用周围的异步包装器会带来任何好处。?
  • @Bezhas 让我们退后一步......而不是在没有适当上下文和猜测的情况下给出示例,我认识的这个人告诉你所有关于异步和等待模式的信息,并且有一些很棒的博客您应该阅读的帖子;)
  • @TheGeneral 自从 4 小时以来我一直在阅读他的博客。我知道他是 THE Async Await 中的佼佼者
  • @Bezhas:您确定要对数据库更新进行“一劳永逸”吗? “一劳永逸”的字面意思是您不在乎操作是否实际发生,而“数据库更新”对我来说听起来很重要。我通常建议不要提前返回,但如果你确实需要there's a well-established pattern to follow
  • @Bezhas:啊。在这种情况下,等待的操作会给你一些可扩展性的好处。如果数据库也是异步的,那就更好了。
【解决方案2】:

异步编程(即使用协程)的目的不是为了提高性能,而是在你不使用它们时改善计算资源。您使用的每个await,都会引入一个“检查点”,执行可能会被“暂停”。

虽然您的逻辑代码“暂停”,但底层线程并没有像在同步执行模型中那样处于休眠状态。这对于基于 (web) 服务的 API 至关重要,在这些 API 中您可以有多个并发请求:当一个请求正在等待 IO(网络或本地)时,底层线程仍然可以处理另一个请求,从而提高 整体服务。每个请求都不会获得任何好处;确实,当编译器将您的代码重写为状态机以处理“暂停”和“恢复”时,它会增加一些开销。

因此,除了基准测试之外,您还应该选择分析的粒度。

【讨论】:

  • 是的,我有点理解,任何在编译时实际上是异步的方法都会变成一个类(状态机),其中 await 变成了检查点。我要问的是在嵌套调用中做很多等待,比如 A() => B()=> C()=>D()。上下文切换会性价比吗?可能是我很累,但在上述情况下可能有 16 个不同的线程完成一个方法。寻找你的答案
  • 切换上下文确实很贵,但“贵多少”不能在纸上评估,你必须对你的代码进行基准测试。另外,请记住,暂停不是确定性的:假设您向DataReader 查询一行,如果组件读取前 10 行,您很可能第一次被暂停,然后,接下来的 9 行您将在本地具有价值,因此无需暂停。有很多因素需要考虑。我最好的猜测是实现这两个版本,然后测量它们(调用它们至少 10K-100K 次)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-15
  • 1970-01-01
  • 2012-11-29
  • 2020-12-29
  • 2017-01-25
  • 2020-04-19
相关资源
最近更新 更多