← Назад к вопросам

Что такое CancellationException?

2.0 Middle🔥 162 комментариев
#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое 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("Основной поток завершён")
}

В этом примере:

  1. Корутина job запускается и приостанавливается на delay(1000).
  2. Через 500 мс вызывается cancelAndJoin().
  3. Функция delay() проверяет статус отмены корутины и бросает CancellationException.
  4. Исключение перехватывается в блоке 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("Тестовая отмена") // Передаём сообщение
}

Важные практические аспекты

  1. Очистка ресурсов: Для освобождения ресурсов используйте блок finally или invokeOnCompletion:
val job = launch {
    val resource = acquireResource()
    try {
        // Работа с ресурсом
        delay(1000)
    } finally {
        resource.release() // Гарантированно выполнится при отмене
    }
}
  1. Неотменяемые операции: Для выполнения кода, который не должен прерываться отменой, используйте withContext(NonCancellable):
finally {
    withContext(NonCancellable) {
        // Этот код выполнится даже при отмене
        delay(100) // И даже suspend-функции здесь работают
        println("Критичная операция завершена")
    }
}
  1. Взаимодействие с async: При отмене родительской корутины все дочерние async также отменяются с CancellationException.

Заключение

CancellationException — это фундаментальный механизм контроля жизненного цикла корутин, обеспечивающий безопасную и предсказуемую отмену асинхронных операций. Понимание его работы критически важно для:

  • Корректной обработки отмены длительных операций
  • Предотвращения утечек ресурсов
  • Построения отказоустойчивых асинхронных систем
  • Реализации корректных стратегий обработки ошибок в конкурентных сценариях

Правильное использование CancellationException позволяет создавать отзывчивые приложения, которые корректно реагируют на прерывание операций (например, отмену загрузки пользователем) и эффективно управляют системными ресурсами.

Что такое CancellationException? | PrepBro