Home > Enterprise >  What is the difference Job.Cancel vs Scope.Cancel in coroutines?
What is the difference Job.Cancel vs Scope.Cancel in coroutines?

Time:10-19

I have been exploring coroutines and I am quite surprised to see that cancel called on Job does not actually cancel the job while cancel called on scope cancels the coroutines launched from the scope immediately. Can anyone explain this?

Job cancel example.

/**
     * This never stops
     */
    @Test
    fun coroutineTest7() = runBlocking {
        val job = launch(Dispatchers.IO) {
            var count = 0
            while(true){
                if(isActive){
                    println("Hello ${count  }")
                }
                else {
                    println("Job Cancelled but this will still get printed")
                }
                Thread.sleep(500)
            }
        }
        Thread.sleep(6000)
        job.cancel()
        Unit
    }

Scope cancel example

/**
     * This gets cancelled after six seconds
     */
    @Test
    fun coroutineTest5() = runBlocking {
        val newScope = CoroutineScope(Dispatchers.IO)
        newScope.launch(Dispatchers.IO) {
            var count = 0
            while(true){
                Thread.sleep(500)
                println("Hello ${count  }")
            }
        }
        Thread.sleep(6000)
        newScope.cancel()
        Unit
    }

CodePudding user response:

In your first code block, your Job does not cooperate with cancellation. It has an infinite loop and does not call any suspend functions that cooperate with cancellation, so there is no way to exit the loop or for the Job to be cancelled. Since runBlocking runs for as long as its child coroutines are running, it waits forever for the Job to be complete.

In your second code block, your Job still doesn't cooperate with cancellation, but since it's launched in a different CoroutineScope instead of runBlocking's scope, runBlocking doesn't wait for the Job to finish. So although your Job is running forever, runBlocking will promptly return after the newScope.cancel() call, thereby freeing your app process to exit and cutting the misbehaving coroutine short. I suspect if you had other long-running tests in this Unit test suite, you may continue to see that job logging during other tests until the process exits.

You can modify the second test to continue waiting after the runBlocking to see that the Job is still continuing to run in the background:

@Test
fun coroutineTest5() {
    runBlocking {
        val newScope = CoroutineScope(Dispatchers.IO)
        newScope.launch(Dispatchers.IO) {
            var count = 0
            while(true){
                Thread.sleep(500)
                println("Hello ${count  }")
            }
        }
        Thread.sleep(6000)
        newScope.cancel()
        println("Cancelled scope")
        Thread.sleep(2000)
    }
    println("Run blocking returned")
    Thread.sleep(2000)
    println("Returning from test")
}
  • Related