Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 950
nilsmagnus Avatar asked Apr 04 '18 13:04

nilsmagnus


People also ask

How do coroutines know to suspend?

The outer async starts a coroutine. When it calls computation() , the inner async starts a second coroutine. Then, the call to await() suspends the execution of the outer async coroutine, until the execution of the inner async 's coroutine is over.

When should you not use coroutines?

Answer: a. You should not use them for any foreground task.

What is suspend in coroutine?

The definition of suspend function: Suspend function is a function that could be started, paused, and resume.

Is runBlocking a suspend function?

We can see here that coroutines launched using runBlocking are not cancelable. Since cancellation occurs at suspension points, and runBlocking coroutines are not suspendable and do not have suspension points, the coroutine was allowed to complete its execution.


2 Answers

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) }
like image 148
Marko Topolnik Avatar answered Nov 05 '22 12:11

Marko Topolnik


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)
        }
      }
    })
  }
}
like image 34
Yuri Schimke Avatar answered Nov 05 '22 12:11

Yuri Schimke