什么时候应该在 ASP.NET MVC 中使用异步控制器?

Posted

技术标签:

【中文标题】什么时候应该在 ASP.NET MVC 中使用异步控制器?【英文标题】:When should I use Async Controllers in ASP.NET MVC? 【发布时间】:2015-08-14 12:28:53 【问题描述】:

我对在 ASP.NET MVC 中使用异步操作有些担心。它什么时候可以提高我的应用程序的性能,什么时候没有

    在 ASP.NET MVC 中随处使用异步操作是否很好? 关于可等待的方法:当我想查询数据库(通过 EF/NHibernate/其他 ORM)时,我应该使用 async/await 关键字吗? 在一个单动作方法中,我可以使用await关键字异步查询数据库多少次?

【问题讨论】:

【参考方案1】:

你可以找到我的MSDN article on the subject helpful;我在那篇文章中花了很多篇幅描述何时应该在 ASP.NET 上使用async,而不仅仅是如何在 ASP.NET 上使用async

我对在 ASP.NET MVC 中使用异步操作有一些顾虑。什么时候可以提高我的应用程序的性能,什么时候不可以。

首先,了解async/await 的全部意义在于释放线程。在 GUI 应用程序上,主要是关于释放 GUI 线程,以便用户体验更好。在服务器应用程序(包括 ASP.NET MVC)上,主要是关于释放请求线程,以便服务器可以扩展。

特别是,它不会:

让您的个人请求更快地完成。事实上,它们会慢一些(只是一点点)。 点击await 时返回调用者/浏览器。 await 只“屈服”于 ASP.NET 线程池,而不是浏览器。

第一个问题是 - 在 ASP.NET MVC 中随处使用异步操作是否很好?

我会说在您进行 I/O 的任何地方都可以使用它。不过,它不一定是有益的(见下文)。

但是,将它用于受 CPU 限制的方法是不好的。有时开发人员认为他们可以通过在他们的控制器中调用Task.Run 来获得async 的好处,这是一个可怕的想法。因为该代码最终通过占用另一个线程来释放请求线程,所以根本没有任何好处(事实上,他们正在承担额外线程切换的代价)!

当我想查询数据库(通过 EF/NHibernate/其他 ORM)时,我应该使用 async/await 关键字吗?

您可以使用任何可用的等待方法。目前大多数主要玩家都支持async,但也有一些不支持。如果您的 ORM 不支持 async,则不要尝试将其包装在 Task.Run 或类似的东西中(见上文)。

请注意,我说的是“您可以使用”。如果您正在谈论具有单个数据库后端的 ASP.NET MVC,那么您(几乎可以肯定)不会从 async 获得任何可扩展性优势。这是因为 IIS 可以处理比单个 SQL 服务器(或其他经典 RDBMS)实例更多的并发请求。但是,如果您的后端更现代 - SQL 服务器集群、Azure SQL、NoSQL 等 - 并且您的后端可以扩展,并且您的可扩展性瓶颈是 IIS,那么您可以从 @ 获得可扩展性优势987654335@.

第三个问题——ONE single action 方法中可以使用 await 关键字异步查询数据库多少次?

只要你喜欢。但是,请注意,许多 ORM 具有每个连接一个操作的规则。特别是,EF 只允许每个 DbContext 进行单个操作;无论操作是同步的还是异步的,都是如此。

另外,请再次记住后端的可扩展性。如果您使用的是 SQL Server 的单个实例,并且您的 IIS 已经能够使 SQLServer 保持满负荷运行,那么对 SQLServer 施加双倍或三倍的压力对您毫无帮助。

【讨论】:

@seebiscuit:在进行(适当的异步)I/O 时,不使用线程。我有一个blog post,它更详细。 IIS 和单个 SQL Server 实例可以处理多少个请求?我怎样才能找到这些数字? @MOD:这取决于配置和硬件。找到这些数字的唯一方法是在您自己的服务器上进行负载测试。 @Flezcano:完全在 CPU 上执行的方法。即,它们不执行 I/O 或阻塞。 对不起,先生,但我不会接受我试图理解的这两个问题。当我研究这个问题时,比如我有 1 个 Web api 端点同时被 1000 个用户调用那么这个端点是否能够为这千个请求中的每一个提供服务,或者我应该让它异步处理 1000 个请求?【参考方案2】:

异步操作方法在操作必须执行多个独立的长时间运行操作时很有用。

AsyncController 类的典型用途是长时间运行的 Web 服务调用。

我的数据库调用应该是异步的吗?

IIS 线程池通常可以处理比数据库服务器更多的并发阻塞请求。如果数据库是瓶颈,异步调用不会加快数据库响应。如果没有节流机制,通过使用异步调用有效地将更多工作分派给不堪重负的数据库服务器只会将更多的负担转移到数据库上。如果您的数据库是瓶颈,那么异步调用就不是灵丹妙药了。

应该看看1和2参考

源自@PanagiotisKanavos cmets:

此外,异步并不意味着并行。异步执行释放了一个 有价值的线程池线程不会阻塞外部资源,对于 没有复杂性或性能成本。这意味着同一台 IIS 机器可以 处理更多并发请求,而不是它会运行得更快。

您还应该考虑阻塞调用以 CPU 密集型自旋等待。在压力时期,阻止呼叫将 导致延迟升级和应用程序池回收。异步调用 避免这种情况

【讨论】:

关于异步、IIS和数据库,博文太老了,这里得出的结论是错误的。 IIS 必须处理比数据库大得多的负载,线程池线程受到限制,长请求队列可能导致 500 个。在等待 IO 时释放线程的任何事情都是有益的。甚至链接也不是关于 OP 要求的内容。事实上,同样的作者已经写道,您应该尽可能使用异步 DB 方法 此外,异步并不意味着并行。异步执行将有价值的线程池线程从外部资源阻塞中解放出来,没有复杂性或性能成本。这意味着 同一台 IIS 机器 可以处理 更多 个并发请求,而不是它会运行得更快。 您还应该考虑阻塞调用以 CPU 密集型自旋等待开始。在压力时期,阻塞呼叫将导致不断升级的延迟和应用程序池回收。异步调用只是避免了这种情况 由于这是公认的答案,因此对大多数人来说是最重要的,删除第二段关于执行时间和并行运行可能是明智的,这与异步无关。我意识到最后有一个注释,但它非常具有误导性。 异步执行仅在线程真正异步到实现的最底层并使用一些回调机制来表示准备就绪并启动一个新线程来切换上下文并处理结果。所以,我不确定在哪种情况下,这种上下文切换/新线程创建比仅在单个线程中等待结果更有效?无论如何,长时间运行的 Web 服务调用是个坏主意,我更喜欢将其卸载到可靠的后台服务并让客户端检查结果,这可能会在几分钟、几小时或几天内出现。【参考方案3】:

在 ASP.NET MVC 中随处使用异步操作好吗?

像往常一样编程,它取决于。沿着特定的道路前进,总会有一个权衡。

async-await 在您知道自己将收到对服务的并发请求并且希望能够横向扩展的地方大放异彩。 async-await 如何帮助扩展?事实上,当您同步地调用异步 IO 调用时,例如网络调用或访问您的数据库,负责执行的当前线程被阻塞,等待请求完成。当您使用async-await 时,您可以让框架为您创建一个状态机,以确保在 IO 调用完成后,您的方法会从中断处继续执行。

需要注意的是,这个状态机有一个微妙的开销。使方法异步不会使其执行得更快,这是一个需要理解的重要因素,也是许多人的误解。

使用async-await 时要考虑的另一件事是它一直是异步的,这意味着您将看到异步渗透到整个调用堆栈,从上到下。这意味着,如果您想公开同步 API,您经常会发现自己复制了一定数量的代码,因为 async 和 sync 不能很好地混合。

当我想查询数据库时,我是否应该使用 async/await 关键字(通过 EF/NHibernate/其他 ORM)?

如果您选择使用异步 IO 调用,那么是的,async-await 将是一个不错的选择,因为越来越多的现代数据库提供商公开了实现 TAP(任务异步模式)的异步方法。

我可以使用多少次await关键字来查询数据库 在一个单一的动作方法中异步?

只要您遵守数据库提供商规定的规则,您想多少就多少。您可以进行的异步调用数量没有限制。如果您有相互独立且可以同时进行的查询,您可以为每个查询创建一个新任务并使用await Task.WhenAll 等待两者完成。

【讨论】:

异步数据库调用不会执行得更快,但允许同一台 IIS 机器处理更多请求,因为线程池线程不会被浪费。它还降低了 CPU 成本,因为阻塞调用实际上是在实际阻塞之前以 CPU 密集型 spinwait 开始的。在高负载情况下,这可能是机器挣扎或故障与回收之间的区别 @PanagiotisKanavos 我知道,我说的不清楚吗? 您的回答没有提到 IIS 细节 - 崩溃/回收场景是始终使用异步的主要原因。没有经历过的人可能不会理解scale out well 可能意味着your server won't die under load。或者可能是once burned ...的情况【参考方案4】:

async 操作在对数据库或某些网络绑定调用执行某些 I\O 操作时最有帮助,其中处理请求的线程将在从您刚刚调用的数据库或网络绑定调用获得答复之前停止.最好将 await 与它们一起使用,它会真正提高应用程序的响应能力(因为在等待 DB 或任何其他类似操作时会停止更少的 ASP 输入\输出线程)。在我的所有应用程序中,每当非常需要对 DB 进行多次调用时,我总是将它们包装在可等待的方法中,并使用 await 关键字调用它。

【讨论】:

【参考方案5】:

我的 5 美分:

    当且仅当您执行 IO 操作时使用async/await,例如 DB 或外部服务 webservice。 总是更喜欢对 DB 进行异步调用。 每次查询数据库时。

附:第 1 点有一些例外情况,但您需要对异步内部机制有一个很好的了解。

另外一个优势是,如果需要,您可以并行执行少量 IO 调用:

Task task1 = FooAsync(); // launch it, but don't wait for result
Task task2 = BarAsync(); // launch bar; now both foo and bar are running
await Task.WhenAll(task1, task2); // this is better in regard to exception handling
// use task1.Result, task2.Result

【讨论】:

【参考方案6】:

如您所知,MVC 支持异步控制器,您应该利用它。如果您的 Controller 执行冗长的操作(可能是基于磁盘的 I/O 或对另一个远程服务的网络调用),如果请求以同步方式处理,则 IIS 线程一直都很忙。结果,线程只是在等待冗长的操作完成。在第一个请求的操作正在进行时,通过服务其他请求可以更好地利用它。这将有助于服务更多并发请求。 您的 Web 服务将具有高度可扩展性,并且不会轻易遇到 C10k problem。 对数据库查询使用 async/await 是个好主意。是的,您可以根据需要多次使用它们。

查看here 以获得出色的建议。

【讨论】:

【参考方案7】:

我的经验是,如今很多开发人员都使用async/await 作为控制器的默认设置。

我的建议是,只有在你知道它对你有帮助时才使用它

原因是,正如Stephen Cleary 和其他人已经提到的那样,它可能会引入性能问题,而不是解决它们,并且它只会在特定情况下对您有所帮助:

高流量控制器 可扩展的后端

【讨论】:

【参考方案8】:

在 ASP.NET MVC 中随处使用异步操作好吗?

在您可以使用异步方法的任何地方这样做都很好,尤其是当您在工作进程级别遇到大量数据和计算操作的性能问题时。否则,不需要,因为单元测试需要强制转换。

关于可等待的方法:我应该使用 async/await 关键字吗? 想要查询数据库(通过 EF/NHibernate/其他 ORM)?

是的,最好对任何数据库操作尽可能使用异步,以避免工作进程级别的性能问题。 请注意,EF 为大多数操作创建了许多异步替代方案,例如:

.ToListAsync()
.FirstOrDefaultAsync()
.SaveChangesAsync()
.FindAsync()

可以使用await关键字查询数据库多少次 在一个单一的操作方法中异步?

天空是极限

【讨论】:

大多数 Web 应用的业务逻辑通常需要高度顺序的操作,因此在大多数情况下,并行进程之间的跳转仅在 Web 请求/工作进程管理的最顶层是可行的,这无法处理无论如何直接使用数据库请求。我真的很想看到一些典型 Web 应用程序的真实示例,在这些示例中,以异步方式处理 DB 查询实际上是一个好主意。

以上是关于什么时候应该在 ASP.NET MVC 中使用异步控制器?的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC中使用异步控制器

为啥总是在 asp.net mvc 中同步异步操作(async await)

ASP.NET MVC 和 WCF

使用 jQuery 和 ASP.NET MVC 异步上传文件,无需表单提交和异步

ASP.NET MVC 4框架揭秘:Controller(3)

ASP.NET MVC 网站开发总结——Ajax异步提交表单之检查验证码