How does Kotlin coroutines know when to yield when making network calls?

Rafoul :

I`m new to Kotlin coroutines and one thing I failed to figure out is, how does the coroutines know when to yield to others when making network calls.

If I understand it right, a coroutine works preemptively, which means it knows when to yield to other coroutines when it has some time-consuming tasks(typically I/O operations) to perform.

For example, let`s say we want to paint some UI which will display data from a remote server, and we have only one thread to schedule our coroutines. We could launch one coroutine to make REST API calls to get the data, while having another coroutine paint the rest of the UI which have no dependency on the data. However, since we have only one thread, there could only be one coroutine running at a time. And unless the coroutine which is used to fetch data preemptively yields while it is waiting for data to arrive, the two coroutines would be executed sequentially.

As far as I know, Kotlin's coroutine implementation does not patch any of existing JVM implementation or JDK network libraries. So if a coroutine is calling a REST API, it should block just like this is done using a Java thread. I'm saying this because I've seem similar concepts in python which are called green threads. And in order for it to work with python`s built-in network library, one must 'monkey-patch' the network library first. And to me this makes sense because only the network library itself knows when to yield.

So could anyone explain how Kotlin coroutine knows when to yield when calling blocking Java network APIs? Or if it does not, then does it mean the tasks mentioned in the example above could not be performed concurrently give a single thread?

Thanks!

Marko Topolnik :

a coroutine works preemptively

Nope. With coroutines you can only implement cooperative multithreading, where you suspend and resume coroutines with explicit method calls. The coroutine singles out just the concern of suspending and resuming on demand, whereas the coroutine dispatcher is in charge of ensuring it starts and resumes on the appropriate thread.

Studying this code will help you see the essence of Kotlin coroutines:

import kotlinx.coroutines.experimental.*
import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    var continuation: Continuation<Unit>? = null
    println("main(): launch")
    GlobalScope.launch(Dispatchers.Unconfined) {
        println("Coroutine: started")
        suspendCoroutine<Unit> {
            println("Coroutine: suspended")
            continuation = it
        }
        println("Coroutine: resumed")
    }
    println("main(): resume continuation")
    continuation!!.resume(Unit)
    println("main(): back after resume")
}

Here we use the most trivial Unconfined dispatcher, which doesn't do any dispatching, it runs the coroutine right there where you call launch { ... } and continuation.resume(). The coroutine suspends itself by calling suspendCoroutine. This function runs the block you supply by passing it the object you can use later to resume the coroutine. Our code saves it to the var continuation. Control returns to the code after launch, where we use the continuation object to resume the coroutine.

The entire program executes on the main thread and prints this:

main(): launch
Coroutine: started
Coroutine: suspended
main(): resume continuation
Coroutine: resumed
main(): back after resume

We could launch one coroutine to make REST API calls to get the data, while having another coroutine paint the rest of the UI which have no dependency on the data.

This actually describes what you'd do with plain threads. The advantage of coroutines is that you can make a "blocking" call in the middle of GUI-bound code and it won't freeze the GUI. In your example you'd write a single coroutine that makes the network call and then updates the GUI. While the network request is in progress, the coroutine is suspended and other event handlers run, keeping the GUI live. The handlers aren't coroutines, they are just regular GUI callbacks.

In the simplest terms, you can write this Android code:

activity.launch(Dispatchers.Main) {
    textView.text = requestStringFromNetwork()
}

...

suspend fun requestStringFromNetwork() = suspendCancellableCoroutine<String> {
    ...
}

requestStringFromNetwork is the equivalent of "patching the IO layer", but you don't actually patch anything, you just write wrappers around the IO library's public API. Pretty much all Kotlin IO libraries are adding these wrappers and there are extension libs for Java IO libs as well. It's also very straightforward to write your own if you follow these instructions.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=36398&siteId=1