Распространение отмены в иерархиях сопрограмм
Разберитесь, как отмена распространяется по связям родительских и дочерних сопрограмм.
«Распространение отмены в иерархиях сопрограмм» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Кооперативная отмена: isActive и ensureActive
- withTimeout и withTimeoutOrNull
- Очистка с finally и NonCancellable
- Распространение отмены в иерархиях сопрограмм