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

В чем разница между supervisorScope и coroutineScope?

2.4 Senior🔥 153 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

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

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

Различие между supervisorScope и coroutineScope

Оба конструктора — supervisorScope и coroutineScope — являются scope-строителями (scope builders) в Kotlin Coroutines, предназначенными для создания ограниченных областей видимости корутин внутри существующего скоупа. Они позволяют запускать несколько дочерних корутин параллельно и ожидать их завершения. Ключевое отличие заключается в стратегии обработки исключений и распространении ошибок между дочерними корутинами.

Основные характеристики coroutineScope

coroutineScope создает область видимости с строгой дисциплиной отмены и распространения ошибок. Принципы работы:

  • Распространение исключений: Если любая дочерняя корутина завершается с исключением (не обработанным внутри), весь coroutineScope немедленно отменяется, и это исключение распространяется к родителю.
  • Отмена родительских корутин: Все другие дочерние корутины внутри того же coroutineScope также отменяются (через CancellationException).
  • Ожидание завершения: Блок coroutineScope не завершится, пока не завершатся все запущенные внутри дочерние корутины.
import kotlinx.coroutines.*

suspend fun exampleCoroutineScope() = coroutineScope {
    launch {
        delay(100)
        println("Child 1 completed")
    }
    launch {
        delay(50)
        throw RuntimeException("Error in Child 2") // Исключение отменит весь scope
    }
    launch {
        delay(200)
        println("Child 3 won't be printed") // Не выполнится из-за отмены
    }
}

// Вызов
fun main() = runBlocking {
    try {
        exampleCoroutineScope()
    } catch (e: RuntimeException) {
        println("Caught exception: ${e.message}") // Поймаем исключение из дочерней корутины
    }
}

Основные характеристики supervisorScope

supervisorScope создает область видимости с супервизорной стратегией, где дочерние корутины работают более независимо:

  • Изоляция исключений: Сбой одной дочерней корутины (с необработанным исключением) не отменяет другие дочерние корутины внутри того же supervisorScope.
  • Отсутствие автоматической отмены родителя: Исключение не распространяется немедленно к родительской корутине — оно должно быть обработано внутри дочерней (например, через try/catch) или через CoroutineExceptionHandler.
  • Частичное завершение: Другие дочерние корутины продолжают работу, даже если одна завершилась с ошибкой.
import kotlinx.coroutines.*

suspend fun exampleSupervisorScope() = supervisorScope {
    launch {
        delay(100)
        println("Child 1 completed")
    }
    launch {
        delay(50)
        throw RuntimeException("Error in Child 2") // Не отменит другие корутины
    }
    launch {
        delay(200)
        println("Child 3 will be printed") // Выполнится, несмотря на ошибку в другой корутине
    }
}

// Вызов
fun main() = runBlocking {
    exampleSupervisorScope()
    delay(300) // Даем время на выполнение всех корутин
}

Сравнительная таблица

АспектcoroutineScopesupervisorScope
Распространение исключенийИсключение в любой дочерней корутине отменяет весь scope и передается родителюИсключение в дочерней корутине не отменяет другие дочерние корутины
Отмена дочерних корутинВсе дочерние корутины отменяются при сбое однойТолько упавшая корутина завершается, остальные продолжают работу
Поведение родителяРодительская корутина может быть отменена (если не обработано исключение)Родительская корутина обычно не страдает от сбоя дочерней
Типичное применениеПараллельные задачи, где все должны завершиться успешно, или отмена всего блока при ошибкеНезависимые задачи, где сбой одной не должен влиять на другие (например, UI-компоненты, фоновые загрузки)

Практические рекомендации по выбору

  • Используйте coroutineScope, когда:

    • Выполняются зависимые параллельные операции, и ошибка в одной делает бессмысленным продолжение других (например, загрузка данных из нескольких источников, где все необходимы).
    • Нужна атомарность блока — либо все операции успешны, либо ни одна.
  • Используйте supervisorScope, когда:

    • Запускаются независимые фоновые задачи, где сбой одной не должен препятствовать выполнению других (например, отправка аналитики, обновление разных виджетов UI).
    • Работаете с жизненным циклом Android-компонентов (например, в ViewModel), где отдельные операции не должны влиять на общий scope.

Важное замечание об исключениях

В supervisorScope исключения из дочерних корутин, запущенных через launch, не будут автоматически перехвачены родителем. Для обработки ошибок нужно использовать:

  • try/catch внутри дочерней корутины.
  • CoroutineExceptionHandler в контексте корутины.
  • async/await с обработкой Deferred (для supervisorScope исключение будет выброшено только при вызове await()).
// Обработка исключений в supervisorScope с async
suspend fun exampleWithAsync() = supervisorScope {
    val deferred1 = async { /* задача 1 */ }
    val deferred2 = async { throw RuntimeException("Error") }
    
    try {
        deferred2.await() // Исключение будет выброшено здесь
    } catch (e: Exception) {
        println("Deferred2 failed: $e")
    }
    
    deferred1.await() // Продолжит работу, если не было отмены извне
}

Заключение

Выбор между supervisorScope и coroutineScope — это выбор между жесткой связанностью (где ошибка одного влияет на всех) и изолированной отказоустойчивостью (где задачи независимы). В Android-разработке supervisorScope часто применяется в корневых скоупах ViewModel или LifecycleScope, чтобы сбои в отдельных операциях (например, сетевых запросах) не приводили к досрочному завершению всего scope компонента. В то же время coroutineScope идеален для инкапсуляции логически связанных параллельных операций, требующих атомарного завершения.

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

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

Различие между supervisorScope и coroutineScope

В Kotlin Coroutines обе функции supervisorScope и coroutineScope являются строительными блоками для структурированного параллелизма, но они принципиально отличаются в обработке ошибок и поведении при отмене (cancellation).

Основное предназначение

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

supervisorScope создаёт scope с супервизорной стратегией, где неудача одной дочерней корутины не влияет на другие. Это позволяет изолировать сбои и продолжать выполнение остальных задач.

Ключевые отличия

АспектcoroutineScopesupervisorScope
Распространение ошибокИсключение в одной корутине отменяет все остальныеИсключение изолируется только в упавшей корутине
Отмена родителяПриводит к отмене всех дочерних корутинТакже приводит к отмене всех дочерних корутин
Типичное использованиеПараллельные задачи, где все должны завершиться успешноНезависимые задачи, где сбои нужно изолировать

Примеры использования

coroutineScope: жёсткая связь корутин

suspend fun processBatch(items: List<Data>): Result {
    return coroutineScope {
        val deferred1 = async { processPart1(items) }
        val deferred2 = async { processPart2(items) }
        
        // Если deferred1 выбросит исключение - deferred2 автоматически отменится
        val result1 = deferred1.await()
        val result2 = deferred2.await()
        
        combineResults(result1, result2)
    }
}

supervisorScope: независимое выполнение

suspend fun sendAnalyticsEvents(events: List<AnalyticsEvent>) {
    supervisorScope {
        events.forEach { event ->
            // Каждая отправка изолирована - если одна упадёт, остальные продолжат
            launch {
                try {
                    sendToServer(event)
                } catch (e: Exception) {
                    logError("Failed to send event: ${event.id}", e)
                }
            }
        }
    }
}

Внутренние механизмы

supervisorScope создаёт SupervisorJob, который изменяет стандартное поведение распространения исключений:

// Упрощённая реализация supervisorScope
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return withContext(SupervisorJob()) {
        block()
    }
}

Практические сценарии применения

Когда использовать coroutineScope:

  • Параллельные вычисления с общим результатом
  • Транзакционные операции, где все шаги должны либо пройти, либо откатиться
  • Загрузка данных из нескольких источников одновременно

Когда использовать supervisorScope:

  • Фоновые задачи (логирование, аналитика)
  • UI-операции, где нежелательно падение всего интерфейса из-за одной ошибки
  • Микросервисные вызовы, где сбои отдельных сервисов не должны ломать всю систему

Важные нюансы

  1. Оба scope отменяются при отмене родительской корутины
  2. supervisorScope не подавляет исключения - они всё равно будут выброшены, но только после завершения scope
  3. Для обработки ошибок в supervisorScope рекомендуется использовать try-catch внутри каждой дочерней корутины
  4. Structured concurrency соблюдается в обоих случаях - дочерние корутины завершаются перед выходом из scope

Рекомендации по выбору

Выбирайте coroutineScope, когда вам нужна сильная связь между параллельными задачами. Используйте supervisorScope, когда задачи независимы и сбои должны быть изолированы. В большинстве UI-приложений supervisorScope более безопасен, так как предотвращает неожиданные падения всего интерфейса.

В чем разница между supervisorScope и coroutineScope? | PrepBro