The answer is: Coroutine does not know about network calls or I/O operations. You have to write the code according to what you want, enclosing heavy work into different coroutines, so they can be executed concurrently, because the default behavior is sequentially.
For example:
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here (maybe I/O)
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // pretend we are doing something useful here (maybe I/O), too
return 29
}
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
will produce something like this:
The answer is 42
Completed in 2017 ms
and doSomethingUsefulOne() and doSomethingUsefulTwo() will be executed sequentially.
If you want concurrent execution you must write instead:
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
that will produce:
The answer is 42
Completed in 1017 ms
as doSomethingUsefulOne() and doSomethingUsefulTwo() will be executed concurrently.
Source: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#composing-suspending-functions
UPDATE:
About where the coroutines are executed we can read in the github project guide https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#thread-local-data:
Sometimes it is convenient to have an ability to pass some thread-local data, but, for coroutines, which are not bound to any particular thread, it is hard to achieve it manually without writing a lot of boilerplate.
For ThreadLocal, asContextElement extension function is here for the rescue. It creates an additional context element, which keep the value of the given ThreadLocal and restores it every time the coroutine switches its context.
It is easy to demonstrate it in action:
val threadLocal = ThreadLocal<String?>() // declare thread-local variable
fun main(args: Array<String>) = runBlocking<Unit> {
threadLocal.set("main")
println("Pre-main, current thread: ${Thread.currentThread()}, threadlocal value: '${threadLocal.get()}'")
val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
yield()
println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
}
job.join()
println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
}
In this example we launch new coroutine in a background thread pool using Dispatchers.Default, so it works on a different threads from a thread pool, but it still has the value of thread local variable, that we've specified using threadLocal.asContextElement(value = "launch"), no matter on what thread the coroutine is executed. Thus, output (with debug) is:
Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
Launch start, current thread: Thread[CommonPool-worker-1 @coroutine#2,5,main], thread local value: 'launch'
After yield, current thread: Thread[CommonPool-worker-2 @coroutine#2,5,main], thread local value: 'launch'
Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'