我对在 ASP.NET MVC 中使用异步操作有些担心。它何时会提高我的应用程序的性能,何时不会?
在 ASP.NET MVC 中随处使用异步操作是否很好?关于可等待的方法:当我想查询数据库(通过 EF/NHibernate/其他 ORM)时,我应该使用 async/await 关键字吗?我可以在一个操作方法中使用 await 关键字异步查询数据库多少次?
您可能会找到我的MSDN article on the subject helpful;我在那篇文章中花了很多篇幅描述何时应该在 ASP.NET 上使用 async
,而不仅仅是如何在 ASP.NET 上使用 async
。
我对在 ASP.NET MVC 中使用异步操作有些担心。什么时候可以提高我的应用程序的性能,什么时候不可以。
首先,了解 async
/await
的全部意义在于释放线程。在 GUI 应用程序上,主要是关于释放 GUI 线程,以便用户体验更好。在服务器应用程序(包括 ASP.NET MVC)上,主要是关于释放请求线程,以便服务器可以扩展。
特别是,它不会:
让您的个人请求更快地完成。事实上,他们会完成(只是一点点)更慢。
当您点击等待时返回调用者/浏览器。 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,那么您可以从 { 1}。
第三个问题 - 我可以在一个单一的操作方法中使用 await 关键字异步查询数据库多少次?
你喜欢多少就多少。但是,请注意,许多 ORM 具有每个连接一个操作的规则。特别是,EF 只允许每个 DbContext 进行单个操作;无论操作是同步的还是异步的,都是如此。
另外,请再次记住后端的可扩展性。如果您正在访问 SQL Server 的单个实例,并且您的 IIS 已经能够使 SQLServer 保持满负荷运行,那么对 SQLServer 施加双倍或三倍的压力根本不会对您有所帮助。
当一个动作必须执行几个独立的长时间运行的操作时,异步动作方法很有用。
AsyncController 类的典型用途是长时间运行的 Web 服务调用。
我的数据库调用应该是异步的吗?
IIS 线程池通常可以处理比数据库服务器更多的并发阻塞请求。如果数据库是瓶颈,异步调用不会加快数据库响应。如果没有节流机制,通过使用异步调用有效地将更多工作分派给不堪重负的数据库服务器只会将更多的负担转移到数据库上。如果您的数据库是瓶颈,那么异步调用将不是灵丹妙药。
源自@PanagiotisKanavos 评论:
此外,异步并不意味着并行。异步执行使有价值的线程池线程免于对外部资源的阻塞,而没有复杂性或性能成本。这意味着同一台 IIS 机器可以处理更多并发请求,而不是它会运行得更快。您还应该考虑阻塞调用以 CPU 密集型自旋等待开始。在压力时期,阻塞呼叫将导致不断升级的延迟和应用程序池回收。异步调用只是避免了这种情况
在 ASP.NET MVC 中随处使用异步操作是否很好?
像往常一样编程,这取决于。沿着某条路走下去,总会有一个权衡。
async-await
在您知道自己会收到对服务的并发请求并且希望能够横向扩展的地方大放异彩。 async-await
如何帮助横向扩展?事实上,当您同步地调用异步 IO 调用时,例如网络调用或访问您的数据库,负责执行的当前线程被阻塞,等待请求完成。当您使用 async-await
时,您可以让框架为您创建一个状态机,以确保在 IO 调用完成后,您的方法会从中断处继续执行。
需要注意的是,这个状态机有一个微妙的开销。使方法异步并不能使其执行得更快,这是一个需要理解的重要因素,也是许多人的误解。
使用 async-await
时要考虑的另一件事是它一直是异步的,这意味着您将看到异步渗透到整个调用堆栈,从上到下。这意味着如果您想公开同步 API,您经常会发现自己复制了一定数量的代码,因为 async 和 sync 不能很好地混合。
当我想查询数据库(通过 EF/NHibernate/其他 ORM)时,我应该使用 async/await 关键字吗?
如果您选择使用异步 IO 调用,那么是的,async-await
将是一个不错的选择,因为越来越多的现代数据库提供程序公开了实现 TAP(任务异步模式)的异步方法。
ONE single action 方法中,我可以使用 await 关键字异步查询数据库多少次?
只要您遵循数据库提供商规定的规则,您想要多少就多少。您可以进行的异步调用数量没有限制。如果您有相互独立且可以同时进行的查询,您可以为每个查询旋转一个新任务并使用 await Task.WhenAll
等待两者完成。
scale out well
可能意味着your server won't die under load
。或者可能是 once burned ...
async
当操作对 DB 或某些网络绑定调用执行某些 I\O 操作时,其中处理请求的线程将在从您刚刚调用的数据库或网络绑定调用获得响应之前停止,这些操作最有帮助。最好将 await 与它们一起使用,它会真正提高应用程序的响应能力(因为在等待 DB 或任何其他类似操作时会停止更少的 ASP 输入\输出线程)。在我的所有应用程序中,每当非常需要对 DB 进行多次调用时,我总是将它们包装在可等待的方法中,并使用 await
关键字调用它。
我的 5 美分:
当且仅当您执行 IO 操作时使用 async/await,例如 DB 或外部服务 web 服务。总是更喜欢对 DB 进行异步调用。每次查询数据库时。
PS 第 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
如您所知,MVC 支持异步控制器,您应该利用它。如果您的控制器执行了一个冗长的操作(可能是基于磁盘的 I/O 或对另一个远程服务的网络调用),如果请求以同步方式处理,则 IIS 线程一直都很忙。结果,线程只是在等待冗长的操作完成。在第一个请求的操作正在进行时,通过服务其他请求可以更好地利用它。这将有助于服务更多并发请求。您的网络服务将具有高度可扩展性,并且不会轻易遇到 C10k problem。对数据库查询使用 async/await 是个好主意。是的,您可以根据需要多次使用它们。
查看 here 以获得出色的建议。
我的经验是,今天很多开发人员使用 async/await
作为控制器的默认设置。
我的建议是,只有当你知道它会对你有帮助时才使用它。
原因是,正如 Stephen Cleary 和其他人已经提到的那样,它可能会引入性能问题,而不是解决它们,并且它只会在特定情况下为您提供帮助:
高流量控制器
可扩展的后端
在 ASP.NET MVC 中随处使用异步操作是否很好?
在可以使用异步方法的任何地方都这样做是很好的,尤其是当您在工作进程级别遇到大量数据和计算操作的性能问题时。否则,不需要,因为单元测试需要强制转换。
关于可等待的方法:当我想查询数据库(通过 EF/NHibernate/其他 ORM)时,我应该使用 async/await 关键字吗?
是的,最好尽可能将异步用于任何数据库操作,以避免工作进程级别的性能问题。请注意,EF 为大多数操作创建了许多异步替代方案,例如:
.ToListAsync()
.FirstOrDefaultAsync()
.SaveChangesAsync()
.FindAsync()
我可以在一个操作方法中使用 await 关键字异步查询数据库多少次?
天空才是极限
不定期副业成功案例分享