NonCancellable coroutine gets cancelled

Viewed 383

I'm trying to experiment with non-cancellable coroutines and I wrote the following code:

fun main(): Unit = runBlocking {
    // launch first coroutine
    coroutineScope {
        val job1 = launch {
            withContext(NonCancellable) {
                val delay = Random.nextLong(from = 500, until = 5000)
                println("Coroutine started. Waiting for ${delay}ms")
                delay(delay)
                println("Coroutine completed")
            }
        }

        delay(300) // let it print the first line
        println("Cancelling coroutine")
        job1.cancelAndJoin()
    }
}

Output:

Coroutine started. Waiting for 1313ms
Cancelling coroutine
Coroutine completed

So far, everything works as expected. However, if I pass the NonCancellable context (or rather, Job) directly in the launch function, the behaviour changes and the coroutine is cancelled:

fun main(): Unit = runBlocking {
    // launch first coroutine
    coroutineScope {
        val job1 = launch(context = NonCancellable) {
            val delay = Random.nextLong(from = 500, until = 5000)
            println("Coroutine started. Waiting for ${delay}ms")
            delay(delay)
            println("Coroutine completed")
        }

        delay(300) // let it print the first line
        println("Cancelling coroutine")
        job1.cancelAndJoin()
    }
}

Output:

Coroutine started. Waiting for 4996ms
Cancelling coroutine

Why is the second snippet producing a different output?

1 Answers

The job you pass as an argument to a launch method is not the job of the launched coroutine, but its parent job.

In the second snippet above, NonCancellable is the parent job of the job1. As the job1 is just a normal job, it's cancellable (but its parent isn't).

Related