【问题标题】:Should I always use async/await in ASP.NET Core API Controller我是否应该始终在 ASP.NET Core API 控制器中使用 async/await
【发布时间】:2020-07-23 23:38:48
【问题描述】:

作为一个例子,我有一个 ASP.NET Core API controller 从服务中获取一些数据和 2 实现控制器方法的可能方法:

使用异步/等待:

[HttpGet]
public async Task<IActionResult> GetSomeDataAsync()
{
   return await someService.GetSomeDataAsync();
}

没有异步/等待:

[HttpGet]
public Task<IActionResult> GetSomeDataAsync()
{
   return someService.GetSomeDataAsync();
}

这两个哪个更好?这里的关键是只有 1 次调用另一个异步方法 (someService.GetSomeDataAsync())。

【问题讨论】:

  • 嗯,async await 非常受欢迎,因为它可以很好地处理各种场景,即当不需要异步运行时,它会同步执行。所以我建议使用asyncs。但是您需要注意一些开销,即在使用该模式时创建状态机。
  • 尝试在GetSomeDataAsync 中抛出异常并比较堆栈跟踪。我认为一个比另一个更具可读性。

标签: c# asp.net-core async-await


【解决方案1】:

根据来自 ASP.NET 团队的ASP.NET Core Performance Best Practices

避免阻塞调用

ASP.NET Core 应用程序应设计为同时处理多个请求。异步 API 允许少量线程通过不等待阻塞调用来处理数千个并发请求。线程可以处理另一个请求,而不是等待长时间运行的同步任务完成。

ASP.NET Core 应用程序中的一个常见性能问题是阻塞可能是异步的调用。许多同步阻塞调用会导致线程池不足和响应时间下降。

  • 使热代码路径异步。
  • 如果异步 API 可用,则异步调用数据访问、I/O 和长时间运行的操作 API。不要使用 Task.Run 使同步 API 异步。
  • 使控制器/Razor 页面操作异步。整个调用堆栈都是异步的,以便从 async/await 模式中受益。

避免在 HttpRequest/HttpResponse 正文上同步读取或写入

ASP.NET Core 中的所有 I/O 都是异步的。服务器实现 Stream 接口,该接口具有同步和异步重载。应该首选异步的,以避免阻塞线程池线程。阻塞线程会导致线程池饥饿。

ReadFormAsync 优于 Request.Form

使用 HttpContext.Request.ReadFormAsync 而不是 HttpContext.Request.Form。 HttpContext.Request.Form 只能在以下条件下安全读取:

  • 已通过调用 ReadFormAsync 读取表单,并且
  • 正在使用 HttpContext.Request.Form 读取缓存的表单值

优化数据访问和 I/O

与数据存储和其他远程服务的交互通常是 ASP.NET Core 应用程序中最慢的部分。高效地读取和写入数据对于良好的性能至关重要。

  • 请异步调用所有数据访问 API。

【讨论】:

    【解决方案2】:

    两者之间的差异 - 在“只做一件异步事情”的场景中 - 是微妙的,主要围绕异常,特别是:如果抛出异常而不是返回一个异常会发生什么错误任务,b:任何异常情况下的堆栈跟踪细节。

    实际上,这两种方法都可以,但我会保持简单,并在大多数应用程序 代码中使用await 语法(而顶级Web 请求处理程序肯定是应用程序代码)。这里有一点机械开销,但在应用程序级别,这真的没什么,所以不用担心。

    但是,如果您正在编写一个库函数,每个请求(网络 IO、字符串处理等)都会被调用数百或数千次 - 那么 值得考虑更积极(和更手动)的方式来优化任务机制。

    【讨论】:

      【解决方案3】:

      返回异步方法的最佳实践...异步。

      这一直被称为异步,请参阅 msdn 指南:

      https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

      【讨论】:

      • 当然,确保您确实需要异步方法并且它不能只是同步,但这是另一个主题。
      • 我不认为“async-all-the-way”意味着你认为它的意思。仅仅通过省略 asyncawait 关键字并不会突然不再使这个异步。它仍然返回一个等待的任务。现在我不得不承认我并不完全确定,但我认为这里添加的所有异步都是在幕后添加另一个(不需要的)状态机。现在您可以说“是的,但最好将 async 关键字显式添加到所有异步方法中”。这当然是一个有效的想法,但个人喜好。
      • @Knoop 我发现与几个团队成员一起工作通常比获得一英寸的表现更明确一点更有成效。 Tbh 整个 TPL 库已经很复杂,因此为如此小的收益添加更多内容对我来说似乎很奇怪。但我认为,如果你对 TPL 有更深入的了解,你似乎宁愿选择不同的方式。感谢您的评论!
      • 那不是我的意思。正如我所说的,从在团队中工作的角度来看,对于总体上清晰的代码,我完全同意这肯定是一个有效的选择(我也是这样做的),但这是一个编码指南选择,而不是技术选择。但是,您的回答似乎暗示这实际上是一种技术选择,因为 async-all-the-way 意味着不要在异步方法中使用阻塞代码,这里绝对不是这种情况。两个版本都是异步的,并遵循异步-all-the-way 原则
      【解决方案4】:

      很难绝对确定 async-await 是帮助还是阻碍了您的控制器操作方法(因为您需要使用不同的负载对其进行测试)但是,作为一般规则,任何访问数据库、文件系统或其他远程 API 的控制器应该是异步的,而执行简单内存计算的控制器可以保持同步。异步调用会增加一点(但只是很少)开销,因此,如果有疑问,请使用异步。

      【讨论】:

      • 问题中显示的两种变体都是异步的;这不是(完全)问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2022-01-27
      • 1970-01-01
      • 1970-01-01
      • 2018-10-24
      • 2011-04-17
      • 1970-01-01
      相关资源
      最近更新 更多