1
votes

Kotlin says

  • runBlocking method blocks the current thread for waiting
  • coroutineScope just suspends, releasing the underlying thread for other usages.
  • hence runBlocking is a regular function and coroutineScope is a suspending function
fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println("Task from runBlocking")
    }

    coroutineScope { // Creates a coroutine scope
        launch {
            delay(500L) 
            println("Task from nested launch")
        }

        delay(100L)
        println("Task from coroutine scope") // This line will be printed before the nested launch
    }

    println("Coroutine scope is over") // This line is not printed until the nested launch completes
}

in above example what i expect is :-

  • runBlocking blocks main thread and launch will executed and it comes to delay(200L)
  • So, underlying coroutine is released and runs coroutineScope and comes to delay(500L) & delay(100L)
  • So, again underlying coroutine is released and it should print println("Coroutine scope is over").

This is what my understanding on runBlocking and coroutineScope. Which is not working as expected.

the Output is

Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

Can anyone kindly explain in a easy way to understand this.

2

2 Answers

3
votes

launch causes the block to be executed asynchronously, so the call to launch returns immediately, and the coroutine continues its running and doesn't wait for the execution of the launched block.

Therefore, immediately after runBlocking is called, the first and the second launch are called one after another, and immediately after that the coroutine is suspended on delay(100L).

After 100ms the coroutine is resumed and prints "Task from coroutine scope", and then the execution of the nested coroutine-scope's block ends. A coroutine-scope always waits for the end of execution of all the jobs it has launched, so it waits here for 500ms.

Meanwhile, the two launched blocks are executed, so "Task from runBlocking" is printed first (after 200ms from the start), and then "Task from nested launch" is printed (after 500ms from the start).

Eventually, after the internal launched job has been completed, the internal coroutine-scope finishes waiting, and the external coroutine continues and prints "Coroutine scope is over".

This is the story. I hope it helps a little to understand how the code is executed and why the order of printing is like that.

1
votes

I modified your code a little

fun main() = runBlocking(Dispatchers.Default) {

    var i = 1
    launch {
        println("Task from runBlocking")
        while (i < 10) {
            delay(30L)
            println(i++)
        }
    }

    coroutineScope { // Creates a coroutine scope
        launch {
            delay(200L)
            println("Task from nested launch")
        }

        delay(100L)
        println("Task from coroutine scope") // This line will be printed before the nested launch
    }

    println("Coroutine scope is over")

}

Output

Task from runBlocking
1
2
3
Task from coroutine scope
4
5
6
Task from nested launch
Coroutine scope is over
7
8
9

the observation i made is,

  • delay(100L) is equal to approximate 3 times delay(30L)

  • delay(200L) is equal to approximate 6 times delay(30L)

So, after 3 Task from coroutine scope and after 6 Task from nested launch is printed.

then exactly after this Coroutine scope is over but you can still see the loop printed 7,8,9.

This is because like runBlocking coroutineScope waits for all its members to execute by suspending underlying threads. But understand, those threads first work on members of coroutineScope not on runBlocking.

Hence, it is printing Task from coroutine scope and Task from nested launch before Coroutine scope is over