Kotlin Academy · Урок

SupervisorJob и Job: изоляция сбоев

Используйте SupervisorJob, чтобы сбой одного дочернего элемента не отменял соседние.

Урок 1 из 413 шагов

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

Распространение сбоев обычного задания

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

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

Обзор SupervisorJob

SupervisorJob меняет это правило: сбой дочерней корутины НЕ отменяет соседние корутины или родительскую. Каждая дочерняя корутина завершается независимо.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(coroutineContext + supervisor)
    scope.launch { throw RuntimeException("child 1 failed") }
    scope.launch { delay(100); println("child 2 still runs") }
    delay(200)
    supervisor.cancel()
}

Построитель supervisorScope

supervisorScope { } создаёт область действия с SupervisorJob в качестве родительского задания. Это идиоматичный способ запуска независимых дочерних корутин.

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val job1 = launch {
            throw RuntimeException("job1 failed")
        }
        val job2 = launch {
            delay(100)
            println("job2 succeeded")
        }
        job1.join()  // wait for job1 (it failed)
        job2.join()  // job2 is unaffected
    }
}

Обработка сбоев дочерних корутин

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        val result = async {
            delay(50)
            throw RuntimeException("async failed")
        }
        try {
            result.await()
        } catch (e: RuntimeException) {
            println("Caught from async: ${e.message}")
        }
        println("Scope continues")
    }
}

Job и SupervisorJob при создании области действия

Передавайте SupervisorJob() при создании CoroutineScope для сервиса или ViewModel, которые должны переживать отдельные сбои дочерних корутин.

import kotlinx.coroutines.*
class MyService {
    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    fun startTask(name: String) {
        scope.launch {
            if (name == "fail") throw RuntimeException("$name failed")
            println("$name done")
        }
    }
    fun stop() = scope.cancel()
}
fun main() = runBlocking {
    val svc = MyService()
    svc.startTask("fail")
    svc.startTask("ok")
    delay(100)
    svc.stop()
}

viewModelScope использует SupervisorJob

Область действия Android viewModelScope работает на основе SupervisorJob. Один неудачный запуск не отменяет остальные корутины ViewModel.

import kotlinx.coroutines.*
// Android ViewModel internals:
// val viewModelScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
// Each launch is independent — one failure doesn't crash the ViewModel
fun main() = runBlocking {
    println("viewModelScope = SupervisorJob + Main")
}

Параллельная декомпозиция с помощью supervisorScope

Используйте supervisorScope вместе с async для параллельных задач, некоторые из которых могут завершиться с ошибкой, не влияя на остальные.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val results = supervisorScope {
        val a = async { delay(50); "result-A" }
        val b = async { throw RuntimeException("B failed") }
        val c = async { delay(30); "result-C" }
        listOf(
            runCatching { a.await() },
            runCatching { b.await() },
            runCatching { c.await() }
        )
    }
    results.forEach { println(it) }
}

Когда использовать обычное задание

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

import kotlinx.coroutines.*
fun main() = runBlocking {
    try {
        coroutineScope {  // regular Job
            val a = async { delay(50); "A" }
            val b = async { throw RuntimeException("B failed") }
            println(a.await())
            println(b.await())  // throws — cancels a too
        }
    } catch (e: RuntimeException) {
        println("Transaction failed: ${e.message}")
    }
}

Распространение исключений в supervisorScope

В supervisorScope исключение передаётся в область видимости только в том случае, если оно выходит за пределы верхнего уровня дочерней корутины, то есть в дочерней корутине нет обработки через try-catch. Родительская корутина его не видит.

import kotlinx.coroutines.*
fun main() = runBlocking {
    supervisorScope {
        // Exception caught inside child — scope unaffected
        launch {
            try { throw RuntimeException("handled") }
            catch (e: Exception) { println("Child caught: ${e.message}") }
        }
        delay(100)
        println("Scope survived")
    }
}

CoroutineExceptionHandler с SupervisorJob

Установите CoroutineExceptionHandler в супервизорной области видимости, чтобы записывать в журнал или обрабатывать необработанные сбои дочерних корутин, не допуская аварийного завершения области видимости.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Uncaught: ${e.message}")
    }
    val scope = CoroutineScope(SupervisorJob() + handler)
    scope.launch { throw RuntimeException("unhandled failure") }
    scope.launch { delay(100); println("still running") }
    delay(200)
    scope.cancel()
}

Итоги изоляции сбоев

Задание: сбой одной дочерней корутины → сбой всех. SupervisorJob: дочерние корутины завершаются с ошибками независимо друг от друга. Используйте супервизор для сервисов и ViewModels, а обычный Job — для транзакционных групп.

import kotlinx.coroutines.*
// Decision table:
// coroutineScope { }   -> Job: all-or-nothing
// supervisorScope { }  -> SupervisorJob: independent failures
// CoroutineScope(SupervisorJob()) -> long-lived service
fun main() = runBlocking { println("Pick the right job for the right scope") }

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

Какая область видимости не позволяет сбою дочерней корутины отменить соседние корутины?

Повторение

Задание: сбой распространяется на соседние корутины. SupervisorJob/supervisorScope: сбои изолированы для каждой дочерней корутины. Используйте supervisorScope для параллельных независимых задач, а обычный coroutineScope — для транзакционных групп.

Можно начать бесплатно

Изучай Kotlin с ИИ-репетитором — бесплатно

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

Курсы
51
Уроки
203

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

Урок «SupervisorJob и Job: изоляция сбоев» бесплатный?

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

Чему я научусь в уроке «SupervisorJob и Job: изоляция сбоев»?

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

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

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

Сколько времени занимает урок «SupervisorJob и Job: изоляция сбоев»?

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

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

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

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

  1. SupervisorJob и Job: изоляция сбоев
  2. CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений
  3. Распространение исключений в async/await
  4. Проектирование устойчивых архитектур сопрограмм
← Назад к Kotlin Academy