nilsmagnus
nilsmagnus

Reputation: 2322

does IO in coroutines cause suspension?

Inside a coroutine I am doing a http-request with OkHttpClient. The request is done from a function that has the suspend keyword:

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

I assume that the function can be suspended on entry since it has the suspend keyword, but will the coroutine also be suspended when doing the http-request? What about other kinds of blocking IO?

Upvotes: 19

Views: 6167

Answers (3)

Marko Topolnik
Marko Topolnik

Reputation: 200168

There's no automagic going on with Kotlin coroutines. If you call a blocking function like HttpReader.get(), the coroutine won't be suspended and instead the call will block. You can easily assure yourself that a given function won't cause the coroutine to suspend: if it's not a suspend function, it cannot possibly do it, whether or not it's called from a suspend function.

If you want to turn an existing blocking API into non-blocking, suspendable calls, you must submit the blocking calls to a threadpool. The easiest way to achieve it is as follows:

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

withContext is a suspend fun that will suspend the coroutine, submit the provided block to another coroutine dispatcher (here IO) and resume when that block is done and has come up with its result.

You can also easily instantiate your own ExecutorService and use it as a coroutine dispatcher:

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

Now you can write

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

Upvotes: 38

Himanshu Yadav
Himanshu Yadav

Reputation: 117

There are two types of IO libraries in JAVA world, using IO or NIO.

You can find more documentation at https://dzone.com/articles/high-concurrency-http-clients-on-the-jvm

The ones using NIO, can theoretically provide true nonblocking suspension unlike IO ones which only offload the task to a separate thread.

NIO uses some dispatcher threads in the JVM to handle the input output sockets using multiplexing (Reactor design pattern). The way it works is, we request the NIO/dispatchers to load/unload something and they return us some future reference. This code can be turned into coroutines easily.

For IO based libraries, coroutine implementation is not true non blocking. It actually blocks one of the threads just like in Java, however the general usage pattern is, to use Dispatcher.IO which is a threadpool for such blocking IO tasks.

Instead of using OkHttpClient, I would recommend using https://ktor.io/docs/client.html

Upvotes: 1

Yuri Schimke
Yuri Schimke

Reputation: 13458

This PR has example code for proper OkHttp coroutines support

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

It uses the thread pools of OkHttp to do the work. The key bit of code is this generic library code.

 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)
        }
      }
    })
  }
}

Upvotes: 1

Related Questions