协同程序中的 IO 会导致暂停吗?

does IO in coroutines cause suspension?

在协同程序中,我正在使用 OkHttpClient 执行 http 请求。该请求是从具有 suspend 关键字的函数完成的:

suspend fun doSomethingFromHttp(someParam:String): Something {
    ...
    val response = HttpReader.get(url)
    return unmarshalSomething(response)!!
}

我假设该函数可以在进入时挂起,因为它有 suspend 关键字,但是协程在执行 http 请求时是否也会挂起?其他类型的阻塞 IO 呢?

Kotlin 协程没有自动魔法。如果你调用像 HttpReader.get() 这样的阻塞函数,协程不会被挂起,而是调用会阻塞。您可以轻松地向自己保证给定的函数不会导致协程挂起:如果它不是 suspend 函数,则它不可能这样做,无论它是否从 suspend 函数中调用。

如果要将现有的阻塞 API 转换为 non-blocking 可暂停调用,则必须将阻塞调用提交给线程池。最简单的实现方法如下:

val response = withContext(Dispatchers.IO) { HttpReader.get(url) }

withContext 是一个 suspend fun,它将暂停协程,将提供的块提交给另一个协程调度程序(此处为 IO),并在该块完成并出现时恢复及其结果。

您也可以轻松实例化您自己的 ExecutorService 并将其用作协程调度程序:

val myPool = Executors.newCachedThreadPool().asCoroutineDispatcher()

现在你可以写了

val response = withContext(myPool) { HttpReader.get(url) }

此 PR 包含正确支持 OkHttp 协程的示例代码

https://github.com/square/okhttp/pull/4129/files

它使用 OkHttp 的线程池来完成工作。代码的关键部分是这个通用库代码。

 suspend fun OkHttpClient.execute(request: Request): Response {
   val call = this.newCall(request)
   return call.await()
 }

 suspend fun Call.await(): Response {
  return suspendCancellableCoroutine { cont ->
    cont.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback {
      override fun onFailure(call: Call, e: IOException) {
        if (!cont.isCancelled) {
          cont.resumeWithException(e)
        }
      }
       override fun onResponse(call: Call, response: Response) {
        if (!cont.isCancelled) {
          cont.resume(response)
        }
      }
    })
  }
}

JAVA世界上有两种类型的 IO 库,使用 IO 或 NIO。

您可以在 https://dzone.com/articles/high-concurrency-http-clients-on-the-jvm

找到更多文档

使用 NIO 的那些,理论上可以提供真正的非阻塞挂起,不像 IO 那些只将任务卸载到一个单独的线程。

NIO 使用 JVM 中的一些调度程序线程来使用多路复用(Reactor 设计模式)处理输入输出套接字。它的工作方式是,我们请求 NIO/dispatchers 到 load/unload 一些东西,他们 return 我们一些未来的参考。这段代码可以很容易地变成协程。

对于基于 IO 的库,协程实现不是真正的非阻塞。它实际上像 Java 中那样阻塞了其中一个线程,但是一般的使用模式是使用 Dispatcher.IO,这是用于此类阻塞 IO 任务的线程池。

我建议使用 https://ktor.io/docs/client.html

而不是使用 OkHttpClient