0Pricing
Kotlin Academy · Урок

Распространение отмены в иерархиях сопрограмм

Разберитесь, как отмена распространяется по связям родительских и дочерних сопрограмм.

«Распространение отмены в иерархиях сопрограмм» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Родительско-дочерняя связь

Когда корутина запускает другую с помощью launch, дочерняя корутина присоединяется к Job родительской. Отмена родительской корутины отменяет всех дочерних.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        launch { delay(1000); println("child 1") }
        launch { delay(1000); println("child 2") }
        delay(1000)
        println("parent")
    }
    delay(50)
    parent.cancel()
    parent.join()
    println("All cancelled")
}

Ошибка дочерней корутины отменяет родительскую

Если дочерняя корутина выбрасывает исключение, не связанное с отменой, она отменяет родительскую корутину и всех соседей — так по умолчанию ведёт себя Job.

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch {
                delay(50)
                throw RuntimeException("child failed")
            }
            launch {
                delay(1000)
                println("sibling — never prints")
            }
        }
    } catch (e: RuntimeException) {
        println("Caught: ${e.message}")
    }
}

Отмена не распространяется вверх

Отмена дочерней корутины NOT отменяет родительскую. Только необработанные исключения приводят к этому. Родительская корутина может отменить дочернюю в любой момент.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val parent = launch {
        val child = launch {
            delay(1000)
            println("child done")
        }
        delay(50)
        child.cancel()  // cancels child
        child.join()
        println("parent still running")  // parent is fine
    }
    parent.join()
}

coroutineScope и GlobalScope

coroutineScope создаёт дочернюю область действия: отмена и ошибки распространяются по иерархии. GlobalScope создаёт корутины без родителя — избегайте его в рабочем коде.

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            launch { delay(50); throw RuntimeException("error") }
        }
    } catch (e: Exception) {
        println("coroutineScope propagated: ${e.message}")
    }
    // GlobalScope.launch { } would NOT propagate to runBlocking
}

Отмена поддерева

Каждый вызов launch или async возвращает Job. Отмените задание, чтобы отменить всё его поддерево, включая любые вложенные запуски.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val root = launch {
        launch {
            launch { delay(1000); println("deep") }
            delay(1000)
        }
    }
    delay(50)
    root.cancel()
    root.join()
    println("Whole subtree cancelled")
}

join() после cancel()

Всегда вызывайте join() после cancel(), чтобы дождаться полной остановки корутины и её дочерних корутин перед продолжением работы.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup ran") }
    }
    delay(50)
    job.cancel()
    job.join()  // waits for finally to complete
    println("Proceeded after join")
}

cancelAndJoin()

job.cancelAndJoin() — это вспомогательная функция, которая отменяет корутину, а затем дожидается её завершения; она эквивалентна последовательному вызову cancel() + join().

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try { delay(1000) }
        finally { println("cleanup") }
    }
    delay(50)
    job.cancelAndJoin()  // cancel + join in one call
    println("Done")
}

Распространение через асинхронные задачи

При использовании async исключения сохраняются в Deferred и выбрасываются при вызове await(). Если их не обработать, они всё равно распространяются на родительскую корутину.

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {
            val d = async { throw RuntimeException("async fail") }
            d.await()  // rethrows here
        }
    } catch (e: RuntimeException) {
        println("Caught from async: ${e.message}")
    }
}

Гарантия структурированной конкурентности

Структурированная конкурентность гарантирует, что после завершения области действия завершены и все её дочерние корутины. Утечек корутин не возникает: они либо завершаются, либо отменяются.

import kotlinx.coroutines.*
suspend fun doWork() = coroutineScope {
    launch { delay(100); println("work 1") }
    launch { delay(200); println("work 2") }
    // both children complete before doWork returns
}
fun main() = runBlocking {
    doWork()
    println("All work done")
}

Схема распространения отмены

Иерархия: отмена родителя → отмена всех дочерних корутин. Отмена дочерней корутины → только её поддерево. Исключение дочерней корутины → отмена родителя → отмена соседних корутин.

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Parent
    launch {
        val c1 = launch { delay(1000); println("c1") }  // child 1
        val c2 = launch { delay(1000); println("c2") }  // child 2
        delay(50)
        c1.cancel()  // only c1 cancelled
        c1.join()
        println("c2 still active: ${c2.isActive}")
        c2.cancelAndJoin()
    }.join()
}

Жизненный цикл CoroutineScope в Android

В Android область viewModelScope отменяется при очистке ViewModel. Все запущенные корутины отменяются автоматически — ручная очистка не требуется.

import kotlinx.coroutines.*
// Pseudocode:
// class MyViewModel : ViewModel() {
//     fun load() = viewModelScope.launch {
//         val data = repo.fetch() // cancelled if VM cleared
//         _state.value = data
//     }
// }
fun main() = runBlocking { println("viewModelScope cancels on ViewModel.onCleared()") }

Быстрая проверка

Что происходит с соседними корутинами, когда дочерняя корутина выбрасывает необработанное исключение в обычном Job?

Итоги

Отмена родителя распространяется на всех дочерних корутин. Ошибка дочерней корутины распространяется на родителя и соседние корутины (при использовании обычного Job). Используйте cancelAndJoin() для корректного завершения. Структурированная конкурентность предотвращает утечки корутин.

Часто задаваемые вопросы

Урок «Распространение отмены в иерархиях сопрограмм» бесплатный?

Да — полный текст урока «Распространение отмены в иерархиях сопрограмм» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Распространение отмены в иерархиях сопрограмм»?

Разберитесь, как отмена распространяется по связям родительских и дочерних сопрограмм. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Распространение отмены в иерархиях сопрограмм»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Кооперативная отмена: isActive и ensureActive
  2. withTimeout и withTimeoutOrNull
  3. Очистка с finally и NonCancellable
  4. Распространение отмены в иерархиях сопрограмм
← Назад к Kotlin Academy