实体框架异步方法是不是使用 ThreadPool 线程?

Posted

技术标签:

【中文标题】实体框架异步方法是不是使用 ThreadPool 线程?【英文标题】:Do Entity Framework async methods consume ThreadPool threads?实体框架异步方法是否使用 ThreadPool 线程? 【发布时间】:2020-09-12 04:46:57 【问题描述】:

我通常在我的 Web 应用程序中使用许多 EF Core 异步方法,如下所示:

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

我们知道,ThreadPool 中的初始线程数默认限制为 CPU 逻辑核心数。用户请求也由ThreadPool 中的线程处理。

我是否应该担心由于我的应用程序中有许多异步调用而处理用户请求或性能问题?

【问题讨论】:

默认情况下,ThreadPool 被限制为最多 32767 个工作线程和最多 1000 个完成端口线程 @SirRufo 这个数字是 MaxLimit Rufo,但是我在问题中提到的应用程序启动时生成的默认线程数 因为这是一个 DB 调用,所以它是 IO 绑定的,所以它会在与 DB 进行 IO 时释放线程,然后它会安排代码运行之后。这可能会在原始线程上拾取或被线程池线程拾取,但无论哪种方式,这都不会产生一些新线程来执行数据库调用。 等待DbContext 上的异步方法NOT会导致使用线程池或其他线程上的任何其他线程。 使用异步重载查询数据库将使处理请求的线程能够服务另一个请求,而不是等待结果从数据库返回。这通常是一件好事,但可能会有一些陷阱,正如@David Browne - Microsoft 的回答中提到的那样。等待的任务完成后,异步方法的其余部分将在 ASP.NET Core 应用程序上下文中的线程池线程上执行。这显然需要一个免费的。在 GUI 应用程序中,它在调度程序线程上执行。 【参考方案1】:

我是否应该担心由于我的应用程序中有许多异步调用而处理用户请求或性能问题?

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

所以 EF 通常不会阻塞您的线程。当你对数据库进行异步调用时,它会释放应用程序的线程来做更多的工作。比如处理额外的请求。如果您对数据库有太多并发请求,每个请求将开始花费更多时间。

当这种情况发生时,您需要有一种机制来降低对数据库的新请求的速度,否则您将进入糟糕的状态。数据库服务器所在的 EG 有 2000 个正在运行的请求,其中大部分是代表已放弃和超时的客户端。并且由于所有旧请求都没有及时处理新请求。

通常,当您将并发增加到某个点时,吞吐量会增加,但超过该点时,整体吞吐量会下降,有时甚至会急剧下降。像这样的:

您可以限制整体并发性以防止吞吐量严重下降。最好尽早让一些请求失败(例如使用 HTTP 503),而不是全部接受它们而不在您的 SLA 中完成任何请求。

使用同步数据库访问的好处之一是它在数据库交互期间占用一个应用程序线程,自动向请求流添加背压。当所有线程池线程都忙时,请求必须等待线程池线程实际上是一件好事。当你全部异步时,这个控件就会消失,你需要考虑替换它。

ASP.NET Core 当前没有内置限制。您的 Web 服务器主机可能有一些,例如,SqlConnection 的连接池限制用于限制每个应用程序实例的并发请求数。但是您必须拥有一些东西,让您能够以有序的方式处理请求量的激增。

【讨论】:

我认为 OP 担心 EF 在后台阻塞线程的可能性,这与异步方法 are expected to behave 的方式相反。而您的答案是:当所有线程池线程都忙时,请求必须等待线程池线程实际上是一件好事。 或者改写为:“如果它阻塞线程,请不要担心,因为有一个饥饿的ThreadPool 是一件好事,因为它有助于控制数据库的并发性。” 但我在这里怀疑,因为您假设每个请求都针对不正确的数据库,并且通过饿死 ThreadPool 我们也饿死它们。应该有一些更好的选择 我认为同步使用 ThreadPool(即阻塞 I/O)并不比异步使用(即异步 I/O)更好。前者 a̶u̶t̶o̶m̶a̶t̶i̶c̶a̶l̶l̶y̶ 隐含地对较低层施加某种限制这一事实与明确施加合理限制相比并没有好处,并且可能会扩展以下层。大型同步 Web 场或小型异步 Web 场之间的数据库有什么区别,如果它的处理能力、内存或带宽都超过了呢?但是,Web 应用程序层的基础架构存在很大差异。 @DavidBrowne-Microsoft,这取决于规模。考虑 IIS 7.5 最高 10,ASP.NET on .NET Framework 4.5 最高 4.8,以及 I/O 绑定的 Web 应用程序或服务。将其中一些转换为异步 I/O 后,每个都导致同时使用 1/500 到 1/1000 的线程。以前,最大 TP 为 2000,它被限制为 2000 个并发请求,现在它可以处理 20000+ 个并发请求,同时最大约 25 个线程。由于共享应用程序池中的内存减少,云节省允许对数据库进行投资。甚至免费和共享的 Azure 托管也成为测试开发版本的选项。 This blog post 也(最初)推荐使用同步调用。但是,此答案和该博客文章中的隐含假设是只有一个数据库后端服务器。在云世界中,async 是一个更自然的默认值。

以上是关于实体框架异步方法是不是使用 ThreadPool 线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何使实体框架异步执行

实体框架异步操作需要十倍的时间才能完成

Spring Boot Async 方法如何使用 ThreadPool 处理请求

多线程异步编程示例和实践-Thread和ThreadPool

.NET Threadpool 工作线程和异步 IO 线程

ThreadPool.QueueUserWorkItem引发的血案,线程池异步非正确姿势导致程序闪退的问题