协同程序中的 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 任务的线程池。
而不是使用 OkHttpClient
在协同程序中,我正在使用 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 任务的线程池。
而不是使用 OkHttpClient