1
votes

I started looking into Coroutines a few days ago. I understand a little bit, but then I saw some lines of code that raised some questions:

import kotlinx.coroutines.*

fun main() = runBlocking {

    launch {

        println("in sub coroutine ${Thread.currentThread().name}")
    }
    println("before coroutine in main ${Thread.currentThread().name}")
    withContext(Dispatchers.IO) {
        println("hello from coroutine ${Thread.currentThread().name}")
        delay(1500)
        println("hello from coutoutine after delay ${Thread.currentThread().name}")
    }
    println("after coroutine in main ${Thread.currentThread().name}")
}

The output is:

before coroutine in main main @coroutine#1

in sub coroutine main @coroutine#2

hello from coroutine DefaultDispatcher-worker-1 @coroutine#1

hello from coutoutine after delay DefaultDispatcher-worker-1 @coroutine#1

after coroutine in main main @coroutine#1

From my understanding, launch creates a new coroutine on the worker thread, thus any normal function on main thread is executed before the launch is completed. If so, I am a bit confused why the withContext code runs before the launch code. Can someone explain?

1
withContext(Dispatchers.IO) suspends the main thread giving the code inside launch block the chance to be executed, while the code inside withContext block runs concurrently. Actually according to your output, launch block gets executed before withContext block. The last line gets executed after withContext block because the main thread resumes execution as soon as withContext finishes. - Glenn Sandoval
Thank you, I could have sworn when I ran this code before, the launch block of code was executed after the with Context code, is there a reason why that happens - Chuki Bom
@jeremy Camry, I ran the code again for like 5 times and the output kept changing, for two tries, the launch code executed first,and for the remaining three it executed after the Dispatch. IO, is there any reason for this lack of uniformity - Chuki Bom
launch block and withContext block run concurrently so that behavior can be expected. Although you shouldn't expect withContext to finish before launch finishes because of the delay call. - Glenn Sandoval
Okay that makes sense, they run on two different dispatchers, I also have the last question I want to ask, delay suspends the current Coroutine right,if it’s in a couroutine scope or runBlocking scope, it can’t leave that block, because al child Coroutines have to be completed? - Chuki Bom

1 Answers

1
votes

launch creates a new coroutine on the worker thread

Be careful when you frame your thoughts according to a sentence like this. A coroutine doesn't run on a given thread the way normal code does. It is much more like pinning a thread to a CPU core. The pinned thread doesn't own the core, the OS just makes sure that, whenever it suspends and then resumes it, it will schedule it to the same CPU core.

If you go through your code with the "scheduling threads to CPU cores" paradigm, you can easily see how your output makes sense:

runBlocking { // Create a context within which "threads" are pinned
              // to a single "core", let's call it "Main Core"
    launch { // Start another "thread" pinned to "Main Core". The "thread" is
             // in a suspended state, waiting for "Main Core" to get free
        println("in sub coroutine ${Thread.currentThread().name}")
    }
    // `launch` is just a function, it completed after creating the new "thread",
    //  move on to the code below it
    println("before coroutine in main ${Thread.currentThread().name}")
    // Start a context where "threads" are pinned to another "core", the 
    // "IO Core". It executes its "threads" concurrently to "Main Core".
    // However, the particular "thread" that creates the context gets suspended
    // until it is done. Other "threads" pinned to "Main Core" can run.
    withContext(Dispatchers.IO) {
        println("hello from coroutine ${Thread.currentThread().name}")
        delay(1500)
        println("hello from coutoutine after delay ${Thread.currentThread().name}")
    }
    // Now the "thread" that created the "IO Core" context can go on.
    println("after coroutine in main ${Thread.currentThread().name}")
}

To this picture you just have to add the fact that the "OS" is unable to preemptively suspend a "thread", only when the "thread" suspends itself the "OS" can take over to make another scheduling decision.