实体框架异步方法会消耗线程池线程吗?

9

我通常在我的 Web 应用程序中使用许多 EF Core 异步方法,就像这样:

await db.Parents.FirstOrDefaultAsync(p => p.Id == id);

我们知道,ThreadPool 中默认的线程数目是受限于 CPU 逻辑内核数的。同时,用户请求也会被 ThreadPool 中的线程处理。

对于我的应用程序中的许多异步调用,我是否需要担心处理用户请求或性能问题?


1
默认情况下,线程池的最大工作线程数限制为32767个,完成端口线程数限制为1000个。 - Sir Rufo
1
@SirRufo 那些数字是MaxLimit Rufo,但应用程序启动时生成的默认线程数是我在问题中提到的。 - Arman Ebrahimpour
3
因为这是一个数据库调用,它受制于IO操作,所以在执行IO操作时,它会释放线程,然后安排之后要运行的代码。该代码可能会在原始线程上执行,也可能会被线程池中的线程执行,但无论哪种方式,都不会生成新的线程来执行数据库调用。 - juharr
3
等待DbContext上的异步方法不会导致使用额外的线程,无论是在线程池中还是其他地方。 - Bradley Uffner
1
使用异步重载来查询数据库将使处理请求的线程能够服务于另一个请求,而不是等待结果从数据库返回。这通常是一件好事,但可能会有一些缺陷,如 @David Browne - Microsoft 的回答中所提到的。一旦等待的任务完成,剩余的异步方法将在 ASP.NET Core 应用程序的线程池线程上下文中执行。这显然需要一个空闲的线程。在 GUI 应用程序中,它在调度程序线程上执行。 - mm8
显示剩余3条评论
1个回答

7

我需要担心应用程序中的许多异步调用导致的用户请求或性能问题吗?

EF Core为存储库提供异步查询接口。是否全程异步,或者某些方法是否会阻塞线程池线程取决于EF提供程序。SQLServer的SqlClient具有不阻塞线程的基于任务的异步方法。大多数其他提供程序也是如此。但例如对于EF内存提供程序,或者可能是SQLite提供程序,它可能是异步/同步,要么同步完成并返回已完成的任务,要么阻塞线程池线程。

因此,EF通常不会阻止您的线程。当您向数据库发出异步调用时,它会释放应用程序的线程以执行更多工作,例如处理其他请求。如果您向数据库发出太多并发请求,则每个请求开始花费更多时间。

当这种情况发生时,您需要有一种机制来减缓向数据库发送新请求的速率,否则您将陷入糟糕的状态。例如,数据库服务器有2000个正在运行的请求,其中大部分代表已经放弃并超时的客户端。由于所有旧请求,新请求无法及时处理。

通常情况下,当您增加并发时,吞吐量会增加到某个点,但超过该点后,总体吞吐量会下降,有时会急剧下降。大致如下:

enter image description here

你需要限制整体并发以防止吞吐量严重降低。最好及早失败一些请求(例如使用HTTP 503),而不是接受所有请求,但在SLA内没有完成。

使用同步数据库访问的好处之一是它会占用应用程序线程的时间来进行数据库交互,自动向请求流添加反压力。当所有线程池线程都很忙时,让请求等待线程池线程实际上是一件好事。当你完全异步化时,这种控制就消失了,你需要考虑如何替换它。

ASP.NET Core目前没有内置的限流功能。你的Web服务器主机可能有一些,例如SqlConnection的连接池限制可用于限制每个应用程序实例的并发请求数量。但你必须有某种东西,可以使你有条理地处理请求量激增。


2
我认为OP担心EF在幕后阻塞线程的可能性,这与异步方法预期的行为相反。你的答案是:“当所有线程池线程都忙时,让请求等待线程池线程实际上是一件好事。”或者换句话说:“不要担心它会阻塞线程,因为饥饿的线程池有助于控制数据库并发性。” - Theodor Zoulias
2
我了解到,ThreadPool 的同步使用(即阻塞 I/O)并不比异步使用(即异步 I/O)更好。前者隐含地对较低层施加某种限制并不是比显式地施加合理的限制更有益,也许可以扩展后续层次。如果处理能力、内存或带宽超过其容量,那么大型同步 Web 农场和小型异步 Web 农场对数据库有什么区别呢?然而,在 Web 应用程序层面存在很大的基础设施差异。 - acelent
3
@DavidBrowne-Microsoft,这取决于规模。考虑使用IIS 7.5到10、.NET Framework 4.5到4.8上的ASP.NET,以及一个I/O绑定的Web应用程序或服务。将其中的一些转换为异步I/O后,每个同时使用的线程数只有以前的1/500到1/1000。之前,最大线程池为2000,同时请求受限于2000个,现在可以处理20000多个并发请求,同时最多使用约25个线程。由于共享应用程序池中内存减少而带来的云节省,使得可以在数据库上进行投资。即使是免费和共享的Azure托管也成为测试开发版本的选择。 - acelent
2
这篇博客文章(https://learn.microsoft.com/en-us/archive/blogs/rickandy/should-my-database-calls-be-asynchronous)也推荐使用同步调用(synchronous calls)。然而,该答案和博客文章中的隐含假设是存在单个数据库后端服务器。在云计算世界中,“async”更为自然和默认。 - Stephen Cleary
1
@StephenCleary 我认为他们两个都没有考虑到高负载应用程序中缓存机制的存在,这会保护使用 async 即使在单个数据库情况下。尽管我理解他们的担忧,但这似乎是几乎有效的。 - Arman Ebrahimpour
显示剩余11条评论

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