如何在 Kotlin 协程中使用阻塞(I/O 绑定)API?

How to use blocking (I/O bound) APIs within Kotlin coroutines?

我正在使用 Ktor 编写 Kotlin 服务器 - 我的请求处理程序是使用 Kotlin 协程编写的。

我的理解是每个请求处理程序在 Ktor 的线程池中 运行,其中包含的线程 比传统的每个请求 1 个线程的池大小少得多 由于协程的 lightweight/suspendable 性质,服务器框架。太棒了!

我遇到的问题是我的应用程序仍然需要与一些阻塞资源(JDBC数据库连接池)交互,但我的理解是,如果我只是直接从请求协程调用这些阻塞 APIs 我最终会遇到 活性问题 - 因为我最终会阻塞所有用于处理我的请求的线程!不太好。

由于我对 Kotlin 和协同程序的世界还比较陌生,我想知道这里是否有人可以给我一些处理这种情况的最佳方法的提示。


我在其他地方看到 Dispatchers.IO 被引用了几次。这被认为是管理这些阻塞调用的最佳方法吗?有什么好的例子吗?

我正在尝试使用的 API 通过传递 Executor 确实允许一些异步性。理想情况下,我还可以将这些调用包装在一个方便、惯用的 Kotlin API 中用于 suspending 事务。

你没看错。在大多数情况下,在协程中你不应该阻塞线程。一个例外是您提到的Dispatchers.IO。这是处理阻塞代码的标准方式,而且非常易于使用:

withContext(Dispatchers.IO) {
    // blocking code
}

withContext()是一个suspend函数,所以你可以把上面想成阻塞转suspend的方式。然而,Dispatchers.IO 并没有真正执行任何魔法 - 它只是使用了更大的线程池,指定用于阻塞。我相信默认情况下它最多创建 64 个线程。

如果您需要执行多个并行阻塞操作,通常最好创建自己的线程池以不阻塞应用程序的其他组件。

如果 IO 库提供异步 API 那么通常最好使用它而不是阻塞 API。但是,在许多情况下,库通过管理自己的内部线程池来进行阻塞来提供异步 API。在那种情况下,使用异步 API 和使用阻塞 API 与 Dispatchers.IO 非常相似。 Dispatchers.IO 可能会更好,因为它在所有 IO 操作中重复使用相同的 IO 线程,并且它可以与指定用于 CPU 计算(Dispatchers.Default)的线程池部分共享线程。