CoroutineScope cancelation

Viewed 1362

I exactly understand how are suspendCoroutine vs suspendCancellableCoroutine work in my samples. But im wondering why println("I finished") (line 13 - second line in viewscope block) executed after i had called viewScope.cancel(). I can fix it with isActive flag before this line but i don't want to check each line. What am i missing there. How i can cancel scope as well ? Thanks

import kotlinx.coroutines.*
import java.lang.Exception
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

fun main() {
    val parentJob = Job()
    val viewScope = CoroutineScope(Dispatchers.IO + parentJob)

    viewScope.launch {
        println(tryMe())
        println("I finished")
    }
    Thread.sleep(2000)
    viewScope.cancel()
    Thread.sleep(10000)
}

suspend fun tryMe() = suspendCoroutine<String> {
    println("I started working")
    Thread.sleep(6000)
    println("Im still working :O")
    it.resume("I returned object at the end :)")
}

suspend fun tryMe2() = suspendCancellableCoroutine<String> {
    println("I started working")
    Thread.sleep(6000)
    println("Im still working :O")
    it.resume("I returned object at the end :)")
}

suspend fun tryMe3() = suspendCancellableCoroutine<String> {
    it.invokeOnCancellation { println("I canceled did you heard that ?") }
    println("I started working")
    Thread.sleep(6000)
    if (it.isActive)
        println("Im still working :O")
    it.resume("I returned object at the end :)")
}
3 Answers

If we just call cancel, it doesn’t mean that the coroutine work will just stop. If you’re performing some relatively heavy computation, like reading from multiple files, there’s nothing that automatically stops your code from running. Once job.cancel is called, our coroutine moves to Cancelling state.

Cancellation of coroutine code needs to be coperative

You need to make sure that all the coroutine work you’re implementing is cooperative with cancellation, therefore you need to check for cancellation periodically or before beginning any long running work. For example, if you’re reading multiple files from disk, before you start reading each file, check whether the coroutine was cancelled or not. Like this you avoid doing CPU intensive work when it’s not needed anymore.

All suspend functions from kotlinx.coroutines are cancellable: withContext, delay etc. So if you’re using any of them you don’t need to check for cancellation and stop execution or throw a CancellationException. But, if you’re not using them, to make your coroutine code cooperative by checking job.isActive or ensureActive()

Coroutine cancellation is co-operative

You should check whether the coroutine is still active before println("I finished") if you wish that statement not to be executed if the coroutine is canceled, like it follows:

if (isActive)
    println("I finished")

Why is that?

Coroutines are not guranteed to be dispatched on another thread. So, while threads provide means to be aborted, which is implemented either at system–level or user–level in the runtime (e.g. the JVM, or ART), coroutines that are not backed by a thread couldn't be canceled anyhow, because the only thing that could be done is throw an exception, but that would abort the whole current execution context (i.e. thread), where other coroutines may be running.
Other answers talk about heavy computations, but that's plainly wrong. No matter what you're doing, whether computationally heavy or not — coroutines can't be forcefully canceled; their cancellation is only a request to be canceled, it's up to the coroutine body to handle cancellation requests using the CoroutineScope property isActive, which gets a boolean indicating whether the work the coroutine is carrying on should continue, if true; or be canceled, if false.

Using suspendCancellableCoroutine is working as expected. suspendCoroutine doesn't check for the cancellation state of the coroutineScope(internally uses safeContinuation) while suspendCancellableCoroutine does checks for the cancellation via CancellableContinuationImpl and it cancels the resume operation.

Related