Cancellation

Cancellation and Exception Handling in Coroutines

Cancellation is cooperative: cancel() flags a coroutine, and its next suspension point throws CancellationException, so finally blocks run. A failing child cancels its siblings (Coroutines vs Promises) unless the scope has a SupervisorJob, as viewModelScope does; a CoroutineExceptionHandler gets the rest.

cancel.kt: cancelAndJoin runs finally; a supervisor keeps siblings aliveKotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
  val sync = launch {
    try { repeat(10) { log("syncing page $it"); delay(100) } }
    finally { log("finally: closing the connection") }
  }
  delay(250); sync.cancelAndJoin()
  val handler = CoroutineExceptionHandler { _, e -> log("handler caught ${e.message}") }
  val screen = CoroutineScope(SupervisorJob() + handler)          // like viewModelScope
  screen.launch { delay(50); error("cover download failed") }
  screen.launch { delay(100); log("prices loaded anyway") }.join()
}
Output
   0 ms [main @coroutine#2] syncing page 0
 135 ms [main @coroutine#2] syncing page 1
 238 ms [main @coroutine#2] syncing page 2
 365 ms [main @coroutine#2] finally: closing the connection
 430 ms [DefaultDispatcher-worker-1 @coroutine#3] handler caught cover download failed
 480 ms [DefaultDispatcher-worker-1 @coroutine#4] prices loaded anyway

Pitfalls: a CPU loop without suspension points ignores cancellation (call ensureActive()); catch (e: Exception) swallows CancellationException; and a failed async throws at await() but also cancels its parent scope.