Search code examples
kotlinkotlinx.coroutines

Why do these coroutines not give the expected output?


I am trying to understand coroutines in Kotlin, but I've hit a bit of a roadblock. In the following example I would expect the following to happen:

  • Prints 0
  • Setup job 1
  • Setup job 2
  • Prints 3
  • Run job 1
  • Waits 1.2 seconds
  • Prints 1
  • Prints 4
  • Run job 2
  • Waits 1 second
  • Prints 2
  • Prints 5
  • Prints 6

But it prints this:

0
3
2
1
4
5
6

I don't really understand why this is happening. I mostly don't understand how the output of job2 can come before 4. I am very confused.

println("0")
runBlocking {
    val job = GlobalScope.launch {
        // launch new coroutine and keep a reference to its Job
        delay(1200L)
        println("1")
    }
    val job2 = GlobalScope.launch {
        // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("2")
    }

    println("3")
    job.join() // wait until child coroutine completes
    println("4")
    job2.join() // wait until child coroutine
    println("5")
}
println("6")

Solution

  • Reading from the documentation of kotlinx.coroutines#launch:

    By default, the coroutine is immediately scheduled for execution. Other start options can be specified via start parameter. See CoroutineStart for details. An optional start parameter can be set to CoroutineStart.LAZY to start coroutine lazily. In this case, the coroutine Job is created in new state. It can be explicitly started with start function and will be started implicitly on the first invocation of join.

    So your coroutine starts immediate after the launch command. This should explain the order of executed commands. The delay in your async coroutines is executed right away while the main thread executes the next statements. 3 before 2 before 1. Then you wait until your first job is finished (printing 1) before printing 4 from the main thread.

    If you want your code to be executed like you expect it, you can add the start = CoroutineStart.LAZY parameter to the launch like this:

    println("0")
    runBlocking {
        val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
            // launch new coroutine and keep a reference to its Job
            delay(1200L)
            println("1")
        }
        val job2 = GlobalScope.launch(start = CoroutineStart.LAZY) {
            // launch new coroutine and keep a reference to its Job
            delay(1000L)
            println("2")
        }
    
        println("3")
        job.join() // wait until child coroutine completes
        println("4")
        job2.join() // wait until child coroutine
        println("5")
    }
    println("6")