How does a nested Kotlin coroutine scope work?

61 views Asked by At

I'm learning Kotlin coroutines and I was trying to understand the coroutine scope. As per the Kotlin guide on structured concurrency,

An outer scope cannot complete until all its children coroutines complete.

I created an example as below:

fun exampleCoroutine() {
        println("Corutine started")
        runBlocking {
            launch {
                delay(2000)
                launch { //child launch
                    println("nested child")
                }
                println("parent")
            }
            launch {
                delay(1000)
                println("in the second coroutine")
            }
            println("Hello")
        }
    }

And I get the below output

Corutine started
Hello
in the second coroutine
parent 
nested child 

As per my understanding on how coroutine scopes work, I assumed that "nested child" would print before "parent". As that's clearly not the case, I want to know why "nested child" is printing after "parent".

And if my understanding is wrong, please feel free to correct me.

1

There are 1 answers

1
Slaw On BEST ANSWER

From the documentation of launch:

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job.

In other words, it launches a coroutine and returns immediately. The launched coroutine executes asynchronously. Note also that launch is not a suspend function.


You quoted:

An outer scope cannot complete until all its children coroutines complete.

Looking at the following segment of your code:

launch { // parent launch
    delay(2000)
    launch { //child launch
        println("nested child")
    }
    println("parent")
}

That quote is saying the coroutine created by the "parent" launch cannot complete until the child coroutine created by the "child" launch completes. The last print statement in the block is not when the coroutine completes. A coroutine completes when the block returns and, as mentioned in the quote, all children coroutines have also completed.

You can see this better if you make use of invokeOnCompletion from the Job objects returned by launch. For example, the following:

import kotlinx.coroutines.*

fun main(): Unit = runBlocking {
    launch {
        delay(1000)
        launch {
            delay(1000)
            println("Child-1 end")
        }.invokeOnCompletion { println("Child-1 complete") }
        println("Parent-1 end")
    }.invokeOnCompletion { println("Parent-1 complete") }

    launch {
        delay(2000)
        println("Parent-2 end")
    }.invokeOnCompletion { println("Parent-2 complete") }
}

Kotlin Playground: https://pl.kotl.in/Oar099RZs

Outputs:

Parent-1 end
Parent-2 end
Parent-2 complete
Child-1 end
Child-1 complete
Parent-1 complete

Note that although the last statement of "Parent-1" is executed before "Child-1" completes, "Parent-1" does not complete until after "Child-1" completes.