Questions about coroutines from a newcomer

Hello,

I started playing with coroutines and I have couple of questions about this tutorial:
https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html

Question 1:
If you remove job.join() from the following snippet, the result does not change. What’s the point of using it? Is there anything harmful if I don’t use it?

    val job = launch {
    repeat(1000) { i ->

            println("I'm sleeping $i ...")

        delay(500L)

    }

}

delay(1300L) // delay a bit

println("main: I'm tired of waiting!")

job.cancel() // cancels the job

job.join() // waits for job's completion 

println("main: Now I can quit.”)

Question 2: In kotlin coroutines, you cannot cancel computation code. Instead you can check the cancelation status with isActive rather than having computation code like in this example:

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {

    var nextPrintTime = startTime

    var i = 0

    while (isActive) { // cancellable computation loop

        // print a message twice a second

        if (System.currentTimeMillis() >= nextPrintTime) {

            println("I'm sleeping ${i++} ...")

            nextPrintTime += 500L

        }

    }

}

delay(1300L) // delay a bit

println("main: I'm tired of waiting!")

job.cancelAndJoin() // cancels the job and waits for its completion

println("main: Now I can quit."

BUT: if you use while(isActive && i<5), it does not respect the computation code, it only respects the isActive. On the contrary, it gets ever more weird if you use while(isActive || i<5), cause in this case it checks both (waiting for i to become 5 before terminating)! Why is that?

PS: I’m testing the aforementioned examples in unit tests in Android Studio using Kotlin 1.3.0.

Regarding question 1:
Cancelling a job does not force the job to stop immediately. Instead, a CancellationException is thrown at the current (if the job is suspended) or next (if it is not) suspension point. That means that additional code may be executed in the job if 1) the job is not currently suspended, 2) the exception is caught, or 3) a finally block is places somewhere. This allows the job to perform any cleanup needed before stopping.

To see the effect of join, try running the below code:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("Cleaning up")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion 
    println("main: Now I can quit.")    
}

Next, try running the same code without the join:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("Cleaning up")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    // job.join() // waits for job's completion 
    println("main: Now I can quit.")    
}