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

Существует ли иерархия в исполнении корутин

1.3 Junior🔥 242 комментариев
#Многопоточность и асинхронность

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

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

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

Иерархия исполнения корутин

Да, в Kotlin Coroutines существует четкая иерархия исполнения, которая является фундаментальной концепцией для управления жизненным циклом, отменой и распространением контекста. Эта иерархия строится на отношении родитель-потомок между корутинами.

Основные принципы иерархии

Иерархия формируется при запуске новых корутин:

  1. Родительская корутина — корутина, из которой запускается новая.
  2. Дочерняя корутина — корутина, запущенная внутри другой.
import kotlinx.coroutines.*

fun main() = runBlocking { // Родительская корутина (runBlocking)
    launch { // Дочерняя корутина 1
        delay(100)
        println("Child 1")
    }
    
    launch { // Дочерняя корутина 2
        delay(50)
        println("Child 2")
    }
    
    println("Parent")
}
// Вывод может быть:
// Parent
// Child 2
// Child 1

Ключевые аспекты иерархии

1. Распространение контекста (Context Propagation)

Дочерние корутины наследуют контекст от родительской, но с возможностью его переопределения. Наследуются элементы CoroutineContext, такие как Job, CoroutineDispatcher, CoroutineName.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = coroutineContext.job
    val parentScope = CoroutineScope(coroutineContext + CoroutineName("ParentScope"))
    
    parentScope.launch(CoroutineName("ChildCoroutine")) {
        println("Parent job: ${coroutineContext.job == parentJob}") // false
        println("Child name: ${coroutineContext[CoroutineName]?.name}") // ChildCoroutine
        println("Dispatcher inherited: ${coroutineContext[CoroutineDispatcher]}")
    }.join()
}

2. Каскадная отмена (Structured Concurrency)

Это самый важный аспект иерархии. При отмене родительской корутины автоматически отменяются все её дочерние корутины.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        launch { // Child 1
            try {
                delay(1000)
                println("Child 1 completed") // Не выполнится
            } catch (e: CancellationException) {
                println("Child 1 cancelled")
            }
        }
        
        launch { // Child 2
            delay(500)
            println("Child 2 completed") // Не выполнится
        }
    }
    
    delay(300)
    parentJob.cancelAndJoin() // Отмена родителя → отмена всех детей
    println("Parent cancelled")
}

3. Ожидание завершения детей

Родительская корутина не завершится, пока не завершатся все её дочерние корутины.

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        repeat(3) { i ->
            launch {
                delay((i + 1) * 200L)
                println("Child $i done")
            }
        }
        println("Parent waiting for children...")
        // Неявный вызов .join() для всех детей
    }.join()
    println("All children completed, parent finished")
}

4. Распространение исключений (Exception Propagation)

Поведение при исключениях зависит от используемого билдера:

  • launch — неперехваченные исключения автоматически отменяют родителя и других детей (если не используется SupervisorJob)
  • async — исключения оборачиваются в Deferred и выбрасываются только при вызове .await()
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Caught: $exception")
    }
    
    val parent = launch(handler) {
        launch {
            throw RuntimeException("Child failed!") // Отменит родителя и sibling
        }
        
        launch {
            delay(1000)
            println("This won't execute") // Не выполнится из-за исключения в sibling
        }
    }
    
    parent.join()
}

SupervisorJob и независимая иерархия

Для создания независимого поведения дочерних корутин используется SupervisorJob или supervisorScope. При этом исключение в одной дочерней корутине не влияет на другие:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(coroutineContext + supervisor)
    
    val child1 = scope.launch {
        delay(100)
        throw RuntimeException("Child 1 failed!")
    }
    
    val child2 = scope.launch {
        delay(200)
        println("Child 2 completed successfully") // Выполнится несмотря на ошибку в child1
    }
    
    delay(300)
    scope.cancel()
}

Практическое значение иерархии

  1. Управление ресурсами — гарантированное завершение всех корутин при отмене родителя
  2. Отладка и трассировка — возможность отслеживать цепочки вызовов через иерархию
  3. Контроль жизненного цикла — согласованное управление корутинами в UI (Android, Desktop)
  4. Предотвращение утечек — автоматическая очистка дочерних корутин

Иерархия исполнения корутин реализует принцип структурной конкурентности (structured concurrency), который обеспечивает предсказуемое поведение, упрощает отладку и предотвращает распространенные ошибки асинхронного программирования, такие как утечки корутин или незавершенные фоновые операции.