A coroutine that never checks for cancellation continues running after its scope is cancelled, consuming CPU and potentially updating a destroyed UI. Tight loops over large data sets, image processing, and computation jobs all need explicit cancellation checkpoints.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/AmolPardeshi99/android-performance-skills/llms.txt
Use this file to discover all available pages before exploring further.
Why cancellation checkpoints matter
Kotlin coroutines use cooperative cancellation: cancellation is not forced on a running coroutine. Instead, the coroutine must check its own cancellation status. When a scope is cancelled — for example, inViewModel.onCleared() or Fragment.onDestroyView() — only coroutines that regularly yield or check their status will stop promptly. A coroutine in a tight CPU loop with no suspension points will keep running until its current work finishes, even if the user has already left the screen.
The problem: tight loops with no suspension point
ViewModel is cleared and its viewModelScope is cancelled, this coroutine continues iterating through every remaining item. On a large data set this wastes CPU cycles and may write to state that no longer has observers.
Fix 1: ensureActive() for a cancellation checkpoint
ensureActive() checks whether the current coroutine’s job is still active. If the scope has been cancelled, it throws CancellationException immediately, unwinding the coroutine cleanly.
Fix 2: yield() for very tight loops
For extremely tight inner loops where even ensureActive() adds measurable overhead, yield() provides both a cancellation checkpoint and a scheduling opportunity — it suspends briefly to let other coroutines run on the same dispatcher.
ensureActive() throws CancellationException if the scope is cancelled but does not suspend — it returns immediately otherwise. yield() also throws CancellationException on cancellation, but additionally suspends the coroutine and hands control back to the dispatcher, giving other coroutines a chance to run. Use ensureActive() when you only need a cancellation check; use yield() when you also want to avoid starving other coroutines on the same thread pool.When cancellation checkpoints matter most
Cancellation responsiveness is critical in these situations:ViewModel.onCleared()—viewModelScopeis cancelled. Any in-progress computation coroutines should stop immediately so they do not write toStateFloworLiveDataafter the ViewModel is destroyed.Fragment.onDestroyView()—viewLifecycleOwner.lifecycleScopeis cancelled. Coroutines updating views must stop before the view hierarchy is torn down to avoidNullPointerExceptionon view references.- Cancelled
async/launchjobs — When a parent job cancels a child job directly, the child must check cancellation to exit promptly rather than finishing its full workload unnecessarily.