Skip to content

Significance of ensureActive co‐operative cancellation

Devrath edited this page Jan 14, 2024 · 2 revisions

header

Background

  • Say you have launched a coroutine and that coroutine calls a normal suspending function.
  • Now say this normal suspending function does not have a withContext or delay in it but does a long iterative call that takes time.
  • Now if we cancel the scope, the scope will get canceled but since the suspending function is called from it and it does not have withContext or delay, It will continue the execution
  • This indicates that the suspending function called is not cooperative

Reason for the behavior explained in the background

  • Suspending functions are just normal functions but have a suspending modifier in addition that ensures that anything that calls it from. another suspending function.

How to make the suspending function co-operative

  • We can explicitly check as below and throw a cancellation exception
if(currentCoroutineContext().isActive){
   throw CancellationException("Exception Occurred")
}
  • Or we can call currentCoroutineContext().ensureActive() which does the above same check

What is the result of making suspending function cooperative

  • Because of this if the coroutine that called the suspending function is cancelled, This automatically cancelled because of this check

Does delay() and WithContext()

  • The above modifiers help internally check that if the coroutine is active or not

Code

class EnsureActiveDemoVm @Inject constructor( ) : ViewModel() {

    private var job: Job? = null


    fun startWithThreadSleep() {
        job = CoroutineScope(Dispatchers.Default).launch {
            startSuspendWithThreadSleep()
        }
    }

    private suspend fun startSuspendWithThreadSleep() {
        try {

            repeat(10) { index ->
                currentCoroutineContext().ensureActive()
                // Simulate some work
                Thread.sleep(500)

                // Check if the coroutine has been canceled
                if (!currentCoroutineContext().isActive) {
                    println("Coroutine canceled at index $index")
                }
                // Continue with the main logic
                println("Working at index $index")
            }
            // Additional logic after the loop
            println("Coroutine completed")
        } catch (e: CancellationException) {
            // Handle cancellation-specific tasks
            println("Coroutine canceled")
        }
    }

    fun cancel(){
        job?.cancel()
    }
}

Output: Here we pressed cancel when the control was at index 3

Working at index 0
Working at index 1
Working at index 2
Coroutine canceled at index 3
Working at index 3
Coroutine canceled
Clone this wiki locally