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

Как отменить job в Couroutin

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

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

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

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

Отмена Job в Kotlin Coroutines

Отмена корутин-джобов — критически важный аспект управления жизненным циклом и ресурсами в асинхронных операциях. Kotlin Coroutines предоставляет несколько механизмов для кооперативной (сотрудничающей) отмены.

Основные способы отмены Job

1. Прямая отмена через cancel()

Каждый Job (задача) имеет метод cancel(), который инициирует её отмену.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("Работаю: $i")
            delay(100)
        }
    }

    delay(500) // Ждём 500 мс
    job.cancel() // Отменяем джоб
    job.join() // Ждём завершения отмены
    println("Основной поток завершён")
}

2. Комбинированный метод cancelAndJoin()

Более удобный способ — совместить отмену и ожидание:

val job = launch { /* ... */ }
delay(500)
job.cancelAndJoin() // Отменяет И ждёт завершения

Кооперативная отмена и проверка статуса

Корутина должна регулярно проверять свой статус для корректной реакции на отмену. Используйте:

isActive — флаг активности джобы

val job = launch {
    while (isActive) { // Проверяем, активна ли ещё джоба
        println("Выполняю работу")
        delay(100)
    }
    println("Корутина отменена, выполняю финализацию")
}

ensureActive() — выбрасывает CancellationException

launch {
    repeat(1000) {
        ensureActive() // Выбросит исключение, если джоб отменён
        // Длительная операция без suspension points
        heavyComputation()
    }
}

Обработка CancellationException

Это исключение является нормальным механизмом завершения корутин:

val job = launch {
    try {
        repeat(1000) { i ->
            println("Итерация $i")
            delay(100)
        }
    } catch (e: CancellationException) {
        println("Корутина отменена: ${e.message}")
        throw e // Важно пробросить дальше
    } finally {
        println("Выполняем финальные действия (очистка ресурсов)")
    }
}

Особые случаи и важные замечания

1. Некорректные блокирующие операции

Код без suspension points (точок приостановки) не проверяет отмену автоматически:

// ❌ ПРОБЛЕМА: этот цикл не отменится
launch {
    var i = 0
    while (i < 1_000_000_000) {
        i++ // Нет проверки отмены!
    }
}

// ✅ РЕШЕНИЕ: добавляем проверку явно
launch {
    var i = 0
    while (i < 1_000_000_000 && isActive) {
        i++
        yield() // Или ensureActive() периодически
    }
}

2. Отмена через CoroutineScope

Отмена скоупа автоматически отменяет все дочерние корутины:

val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch { /* ... */ }
val job2 = scope.launch { /* ... */ }

// Отменяем ВСЕ корутины в этом скоупе
scope.cancel()

3. withTimeout и withTimeoutOrNull

Автоматическая отмена по таймауту:

try {
    val result = withTimeout(1000) { // Таймаут 1 секунда
        longRunningOperation()
    }
} catch (e: TimeoutCancellationException) {
    println("Операция превысила лимит времени")
}

4. Невозможность выполнения кода в finally после отмены

Код в блоке finally может не выполниться, если диспетчер отменил корутину:

launch {
    try {
        // Длительная работа
    } finally {
        // ❌ Этот код может не выполниться!
        delay(1000) // Приостановка в finally может быть запрещена
        withContext(NonCancellable) { // ✅ Решение
            delay(1000) // Теперь безопасно
            println("Критическая очистка выполнена")
        }
    }
}

Практические рекомендации

  1. Всегда проектируйте корутины с учётом отмены — используйте isActive, ensureActive() или yield() в длительных циклах без suspension points.

  2. Очищайте ресурсы в finally или invokeOnCompletion:

job.invokeOnCompletion { cause: Throwable? ->
    cause?.let { println("Завершено с исключением: $it") }
    cleanupResources()
}
  1. Используйте структурированный параллелизм — создавайте корутины в coroutineScope, withContext или supervisorScope, чтобы автоматически управлять их жизненным циклом.

  2. Помните об исключенияхCancellationException является нормальным механизмом завершения и обычно не должен логироваться как ошибка.

Отмена в Coroutines — это кооперативный процесс, требующий участия как вызывающего кода (вызов cancel()), так и самой корутины (регулярная проверка статуса отмены). Правильная реализация отмены предотвращает утечки ресурсов и обеспечивает отзывчивость приложения.

Как отменить job в Couroutin | PrepBro