Significance of ensureActive co‐operative cancellation - devrath/KotlinAlchemy GitHub Wiki
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
withContextordelayin 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
withContextordelay, 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