【问题标题】:When using "Async" Methods are there Threads of the Threadpool blocked?使用“异步”方法时,线程池的线程是否被阻塞?
【发布时间】:2017-06-20 19:04:21
【问题描述】:

AFAIK 以下是正确的:

长时间阻塞线程池的一个线程并不是一个好主意(因为它们是有限的)。 任务使用线程池的线程,因此是短期的,而不是长期的阻塞操作。 异步方法(如 Entity Framework 中的 FindAsync)返回一个您可以等待(或等待)以接收结果的任务。

我不明白的:

如果我打电话给例如FindAsync 是否只是创建了一个在 ThreadPool 线程上运行并调用非异步 find 方法的任务(阻塞 ThreadPool 线程直到 find 返回)?或者是否涉及更深层次的操作系统机制,并且在 Find 方法返回之前没有使用 ThreadPool 线程?

原因:

如果变体 1 成立,则调用 FindAsync 方法或自己启动任务与在其中调用 Find 方法没有区别。

如果变体 2 成立,则存在差异,因为启动一个调用 Find 方法的任务将长期阻塞一个 ThreadPool 线程,而调用 FindAsync 则不会。

【问题讨论】:

  • Stephen Cleary 写了一篇精彩的文章There Is No Thread,您可能想读一读。请记住TaskCompletionSource 的存在——它的存在是为了允许创建绝对在线程池上运行的Tasks
  • 在这种特定情况下,下面的 SqlClient 提供真正的异步执行,而不使用后台线程。 IO(包括网络调用)在 Windows 中本质上是异步的。线程阻塞是模拟的。 ADO.NET 使用 IO 完成端口与数据库服务器通信,并在收到响应后恢复处理。其他提供者(例如 MySQL 的)通过启动一个新线程来伪造异步

标签: .net threadpool


【解决方案1】:

长时间阻塞线程池的一个线程并不是一个好主意(因为它们是有限的)。

线程池会通过注入一个新的线程来响应一个“丢失”的线程,所以长时间阻塞一个线程池线程并不是特别成问题。 (当然,最好不要一开始就阻塞线程池线程)。

任务使用线程池的线程,因此是短期的,而不是长期的阻塞操作。

这仅适用于调度到线程池的委托任务。许多关于任务的旧(.NET 4 时代)文档都假设您使用的是在线程池上调度的委托任务,因此这是对任务的常见误解一般

在现代世界中,委托任务更为罕见;如今,承诺任务更为常见。承诺任务只是充当“信号”,意思是“完成的事情”。有关委托任务和承诺任务的更多信息,请参阅我在 Task OverviewTask status 上的博客文章。

(此外,委托任务可以安排到任何类型的TaskScheduler;它们不只是用于线程池任务)。

异步方法(如 Entity Framework 中的 FindAsync)返回一个您可以等待(或等待)以接收结果的任务。

这里需要区分使用async 实现其的方法和以*Async 结尾并旨在与await 一起使用的TAP methods。这两者通常一起出现,但并非总是如此。

async 总是返回一个 Promise 任务,而不是一个委托任务。此外,通常预计*Async 方法将返回 Promise 任务,但某些方法会返回委托任务。当方法这样做时,我称之为“假异步”,因为它只是同步阻塞另一个线程。

如果我打电话给例如FindAsync 是否只是创建了一个在 ThreadPool 线程上运行并调用非异步 find 方法的任务(阻塞 ThreadPool 线程直到 find 返回)?

一般情况下不会。 .NET 提供的几乎所有 *Async 方法都返回 Promise 任务。

或者是否涉及更深层次的操作系统机制,并且在 Find 方法返回之前没有使用 ThreadPool 线程?

大多数 .NET *Async 方法返回的 Promise 任务在后台使用 I/O 完成端口,正如我在文章 There Is No Thread 中所描述的那样。

如果变体 1 成立,则调用 FindAsync 方法或自己启动任务与在其中调用 Find 方法没有区别。

如果变体 2 成立,则存在差异,因为启动一个调用 Find 方法的任务将长期阻塞一个 ThreadPool 线程,而调用 FindAsync 则不会。

在一般情况下,您希望使用*Async 方法,这样可以完全避免阻塞任何线程。

但是,正如其他人在 cmets 中指出的那样,实体框架的这个特定示例有点复杂。实体框架本身是异步不可知的;它建立在“提供者”之上,可能支持也可能不支持异步。 Microsoft 的 SqlClient 提供程序确实支持异步,因此FindAsync 与 SQL Server 对话将异步正常工作。但是,其他提供程序可能不会(在撰写本文时,SQLite 是一个不支持异步的通用提供程序),并且对于这些提供程序,像 FindAsync 这样的“异步”API 实际上是通过阻塞线程池线程来实现的。

因此,在一般情况下,“变体 2”是正确的;但是对于您的 FindAsync 的特定示例,它们都是正确的。

【讨论】:

  • 谢谢。这正是我需要的提示。 :-)
猜你喜欢
  • 2022-08-19
  • 1970-01-01
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
  • 2015-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多