Alexander
Alexander

Reputation: 253

`delay` doesn't throw `CancellationException` when CoroutineScope is cancelled

Documentation for the delay function states: "This suspending function is cancellable. If the Job of the current coroutine is cancelled or completed while this suspending function is waiting, this function immediately resumes with CancellationException."

But I'm not seeing it throw a CancellationException. In fact the delay waits for the full 10 seconds before proceeding.

Why?

import kotlinx.coroutines.*
import java.util.concurrent.CancellationException

class MyClass {
    private val scope = CoroutineScope(SupervisorJob() + CoroutineName("X coroutine"))

    init {
        scope.launch {
            delay(500)
            println("cancelling the scope")
            scope.cancel(CancellationException("cancelling the scope"))
        }
    }

    suspend fun callScopedFunction(
        func: suspend CoroutineScope.() -> Int
    ): Int {
        if (scope.isActive) {
            println("callScopedFunction scope is active")
        }
        return scope.func()
    }
}

fun main() {
    runBlocking {
        val result = MyClass().callScopedFunction {
            println("coroutineContext.job.isCancelled: ${coroutineContext.job.isCancelled}")
            delay(10000)
            println("coroutineContext.job.isCancelled: ${coroutineContext.job.isCancelled}")
            250
        }
        println(result)
    }
}

Output:

callScopedFunction scope is active
coroutineContext.job.isCancelled: false
cancelling the scope
coroutineContext.job.isCancelled: true
250

Upvotes: 0

Views: 94

Answers (1)

Tenfour04
Tenfour04

Reputation: 93511

You have never launched any coroutines in the scope that you cancel besides the initial one that cancels its own scope. Notice the launch function is never used in your code anywhere else.

scope.func() is essentially passing scope as a parameter (this) to the func suspend function, but it’s up to the implementing lambda to decide how to use it. And you didn’t use it at all in the lambda.

Your delay(10000) call is made within the runBlocking’s coroutine scope since this lambda is called from that scope.

A corrected version would be something like this (but I didn’t test it):

suspend fun callScopedFunction(
    func: suspend () -> Int
): Int {
    if (scope.isActive) {
        println("callScopedFunction scope is active")
    }
    return scope.async { func() }.await()

Upvotes: 3

Related Questions