Что такое CancellationException?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое CancellationException?
CancellationException — это исключение, которое бросается при попытке выполнить операцию в отменённой корутине (или другом отменяемом контексте выполнения). Это ключевой механизм кооперативной отмены (cooperative cancellation) в Kotlin Coroutines, обеспечивающий безопасное и контролируемое завершение асинхронных операций.
Основное назначение и принцип работы
CancellationException является подклассом java.util.concurrent.CancellationException. Его главная цель — сигнализировать, что корутина была отменена, и прервать её выполнение, не маскируя другие исключения. Когда вызывается cancel() на Job или отменяется Scope, внутренний механизм Coroutines устанавливает флаг отмены и, в большинстве случаев, бросает CancellationException в точке приостановки (suspend point).
Важнейшая особенность: CancellationException считается "нормальным" завершением корутины и не обрабатывается стандартными механизмами логирования ошибок (например, не приводит к выводу в UncaughtExceptionHandler по умолчанию).
Пример возникновения исключения
Рассмотрим простой пример, где CancellationException возникает явно:
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
delay(1000) // Точка приостановки
println("Эта строка не будет выполнена")
} catch (e: CancellationException) {
println("Корутина отменена: ${e.message}")
throw e // Важно повторно бросить исключение!
}
}
delay(500) // Ждём 500 мс
job.cancelAndJoin() // Отменяем корутину
println("Основной поток завершён")
}
В этом примере:
- Корутина
jobзапускается и приостанавливается наdelay(1000). - Через 500 мс вызывается
cancelAndJoin(). - Функция
delay()проверяет статус отмены корутины и бросаетCancellationException. - Исключение перехватывается в блоке
catch, после чего обязательно пробрасывается дальше (это важно для корректной работы механизма отмены).
Ключевые характеристики и правила использования
- Наследование:
CancellationException ← IllegalStateException ← RuntimeException - Кооперативная отмена: Корутина должна периодически проверять статус отмены через вызовы функций приостановки (
delay(),yield(),withContext()и др.) или явно проверятьisActive. - Повторное выбрасывание: При перехвате CancellationException всегда нужно пробрасывать его дальше, иначе отмена может работать некорректно:
try {
someSuspendFunction()
} catch (e: CancellationException) {
// Логирование или очистка ресурсов
throw e // Обязательно!
}
- Отличие от других исключений: В отличие от других исключений, CancellationException:
- Не логируется по умолчанию в
CoroutineExceptionHandler. - Не приводит к аварийному завершению родительской корутины.
- Может содержать сообщение с причиной отмены (через
job.cancel("Причина")).
- Не логируется по умолчанию в
Проверка отмены в CPU-интенсивных операциях
В функциях, которые не вызывают suspend-функции, нужно явно проверять статус отмены:
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
for (i in 1..1000) {
if (!isActive) { // Явная проверка
throw CancellationException("Цикл прерван")
}
// CPU-интенсивная операция
println("Вычисление $i")
}
}
delay(10)
job.cancel()
}
Отмена с причиной и обработка разных типов исключений
Начиная с Kotlin 1.4, можно передавать причину отмены и различать типы исключений:
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
when (exception) {
is CancellationException -> println("Корутина отменена: ${exception.message}")
else -> println("Произошла ошибка: $exception")
}
}
val scope = CoroutineScope(Job() + handler)
val job = scope.launch {
try {
delay(1000)
} finally {
println("Выполняется блок finally")
}
}
delay(100)
job.cancel("Тестовая отмена") // Передаём сообщение
}
Важные практические аспекты
- Очистка ресурсов: Для освобождения ресурсов используйте блок
finallyилиinvokeOnCompletion:
val job = launch {
val resource = acquireResource()
try {
// Работа с ресурсом
delay(1000)
} finally {
resource.release() // Гарантированно выполнится при отмене
}
}
- Неотменяемые операции: Для выполнения кода, который не должен прерываться отменой, используйте
withContext(NonCancellable):
finally {
withContext(NonCancellable) {
// Этот код выполнится даже при отмене
delay(100) // И даже suspend-функции здесь работают
println("Критичная операция завершена")
}
}
- Взаимодействие с
async: При отмене родительской корутины все дочерниеasyncтакже отменяются с CancellationException.
Заключение
CancellationException — это фундаментальный механизм контроля жизненного цикла корутин, обеспечивающий безопасную и предсказуемую отмену асинхронных операций. Понимание его работы критически важно для:
- Корректной обработки отмены длительных операций
- Предотвращения утечек ресурсов
- Построения отказоустойчивых асинхронных систем
- Реализации корректных стратегий обработки ошибок в конкурентных сценариях
Правильное использование CancellationException позволяет создавать отзывчивые приложения, которые корректно реагируют на прерывание операций (например, отмену загрузки пользователем) и эффективно управляют системными ресурсами.