Как работает CancellationException в Kotlin coroutines
Что проверяют
Понимаете ли вы, что отмена в корутинах кооперативная и держится на особом исключении, которое нельзя глотать.
Как отвечать
Отмена реализована через исключение. Когда область или задачу отменяют, корутина, дойдя до точки приостановки, получает CancellationException, и та раскручивает её стек, выполняя блоки finally.
Особенность в том, что это исключение считается штатным завершением, а не ошибкой: оно не отменяет родителя и не попадает в обработчик исключений корутины. Именно поэтому отмена одной дочерней задачи не роняет всю область.
Отсюда главное правило: CancellationException нельзя перехватывать и гасить. Общий catch (e: Exception) вокруг приостанавливаемого кода ломает отмену, и корутина продолжает работать после того, как её экран уже уничтожен.
try {
repository.load()
} catch (e: CancellationException) {
throw e // пробрасываем, иначе отмена перестанет работать
} catch (e: Exception) {
showError(e)
}Второе следствие: отмена кооперативная. Корутина, которая занята вычислением и не доходит до точек приостановки, не отменится. Проверять состояние надо самому через ensureActive() или yield().
Третье: в finally уже нельзя вызывать приостанавливаемые функции, контекст отменён. Для освобождения ресурсов с приостановкой есть withContext(NonCancellable).
Где спотыкаются
Ловят все исключения одним catch и незаметно ломают отмену.
Ждут, что отменится долгий цикл без точек приостановки.
Пытаются писать в базу в finally после отмены и получают ещё одно исключение.
Считают отмену ошибкой и показывают пользователю сообщение при обычном уходе с экрана.