vrwim
vrwim

Reputation: 14320

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:

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")

Upvotes: 2

Views: 903

Answers (2)

leonardkraemer
leonardkraemer

Reputation: 6803

This is what happens:

  1. print 0
  2. enter runBlocking, which blocks the current thread until the block is finished.
  3. setup and launch job 1; The job starts execution immediately and is suspended for 1200ms by the delay statement.
  4. setup and launch job2; The job starts execution immediately and is suspended for 1000m by delay statement.
  5. print 3
  6. wait (execution is suspended) until job 1 finished (job.join()).
  7. after 1000 ms job2 prints 2 and finishes
  8. after 1200 ms job1 prints 1 and finishes
  9. print 4 (because job 1 has finished)
  10. wait (execution is suspended) until job2 finished (job2.join()). This returns immediately, as job2 has already finished.
  11. print 5
  12. print 6, after the runBlocking-block finishes and the thread where it was started continues.

You may read https://kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html to get a better insight into suspending functions. What helped me understand coroutines was this video: https://www.youtube.com/watch?v=_hfBv0a09Jc

Upvotes: 4

Simulant
Simulant

Reputation: 20112

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")

Upvotes: 3

Related Questions