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

Что произойдет с родительским scope если завершится дочерняя корутина?

2.0 Middle🔥 171 комментариев
#Kotlin основы

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

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

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

Влияние завершения дочерней корутины на родительский scope

В Kotlin корутинах поведение родительского scope при завершении дочерней корутины зависит от типа запуска (launch builder) и контекста выполнения. Рассмотрим ключевые варианты.

1. Независимое завершение (не влияет на родителя)

При стандартном запуске через launch в том же scope дочерняя корутина выполняется независимо. Родительский scope продолжит работу даже если дочерняя завершится (обычно или с ошибкой).

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentScope = this
    
    val childJob = parentScope.launch {
        delay(500)
        println("Дочерняя корутина завершилась")
    }
    
    println("Родительский scope активен")
    delay(1000)
    println("Родительский scope тоже завершился")
}

Здесь родительский scope (runBlocking) продолжает работу после завершения childJob.

2. Влияние через SupervisorJob

Если scope использует SupervisorJob, завершение дочерней корутины (особенно с исключением) не влияет на другие дочерние корутины и на сам scope.

val supervisorScope = CoroutineScope(SupervisorJob())
supervisorScope.launch {
    throw Exception("Ошибка в дочерней!")
}
supervisorScope.launch {
    delay(1000)
    println("Эта корутина продолжит работу несмотря на ошибку в соседней")
}

3. Влияние через coroutineScope builder

Внутри функции coroutineScope создается новый под-scope, который ожидает завершения всех своих дочерних корутин. Если дочерняя завершится с ошибкой, этот под-scope завершится и распространит исключение на родителя.

fun main() = runBlocking {
    try {
        coroutineScope {
            launch {
                delay(100)
                throw RuntimeException("Дочерняя ошибка")
            }
            launch {
                delay(200)
                println("Эта корутина не выполнится")
            }
        }
    } catch (e: Exception) {
        println("Родитель поймал исключение: ${e.message}")
    }
}

Здесь coroutineScope завершится при ошибке в первой дочерней корутине, и исключение "поднимется" в родительский runBlocking.

4. Влияние через async и исключения

При использовании async исключение в дочерней корутине будет "запечатано" в Deferred и возникнет только при вызове await().

fun main() = runBlocking {
    val deferred = async {
        delay(100)
        throw ArithmeticException("Ошибка в async")
    }
    
    delay(300)
    
    try {
        deferred.await()
    } catch (e: Exception) {
        println("Исключение получено при await: ${e.message}")
    }
}

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

Ключевые выводы:

  • Обычный launch в общем scope: завершение дочерней корутины (даже с ошибкой) не завершает родительский scope.
  • SupervisorJob: изолирует исключения, scope продолжает работу.
  • coroutineScope builder: под-scope завершается при завершении любой дочерней корутины и влияет на родительскую корутину.
  • async: исключение не влияет на scope до момента вызова await().
  • Главное правило: родительский scope продолжит работу, если не используется структура, которая явно распространяет исключения (как coroutineScope), или если не все дочерние корутины являются частью такого под-scope.

Таким образом, в большинстве случаев родительский scope остается активным после завершения дочерней корутины. Проблемы возникают только при использовании определенных конструкций, которые предназначены для синхронизации и распространения ошибок.