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

Что такое SupervisorScope?

2.2 Middle🔥 131 комментариев
#Многопоточность и асинхронность

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

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

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

# SupervisorScope в Kotlin Coroutines

Общее понятие

SupervisorScope — это корутин-скоп (область видимости корутин), который реализует стратегию супервизора (supervisor) для обработки исключений в иерархии корутин. В отличие от обычного CoroutineScope, который использует стратегию отмены по принципу "родитель-ребенок", SupervisorScope позволяет дочерним корутинам завершаться независимо при возникновении ошибок.

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

Отличия от обычного CoroutineScope

import kotlinx.coroutines.*

fun main() = runBlocking {
    // Обычный scope - при ошибке в дочерней корутине отменяются все
    val regularScope = CoroutineScope(Job())
    
    // SupervisorScope - дочерние корутины независимы
    val supervisorScope = CoroutineScope(SupervisorJob())
    
    // Или используя builder функцию
    supervisorScope {
        // Этот блок создает SupervisorScope
    }
}

Ключевые особенности:

  1. Независимость дочерних корутин - ошибка в одной дочерней корутине не приводит к отмене других дочерних корутин
  2. Родительская корутина продолжает работу даже при сбое дочерних
  3. Исключения не распространяются автоматически на родительскую корутину
  4. Требуется явная обработка исключений в дочерних корутинах

Практическое использование

Создание SupervisorScope

import kotlinx.coroutines.*

suspend fun exampleSupervisorScope() {
    // Способ 1: Использование функции supervisorScope
    supervisorScope {
        launch {
            // Дочерняя корутина 1
            delay(100)
            throw RuntimeException("Ошибка в корутине 1")
        }
        
        launch {
            // Дочерняя корутина 2 продолжит работу
            delay(200)
            println("Корутина 2 выполнена успешно")
        }
    }
    
    // Способ 2: Создание scope с SupervisorJob
    val customSupervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
}

Сравнение поведения

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("=== Пример с обычным scope ===")
    try {
        coroutineScope {
            launch {
                delay(100)
                throw RuntimeException("Ошибка!")
            }
            launch {
                delay(200)
                println("Эта корутина не выполнится")
            }
        }
    } catch (e: Exception) {
        println("Поймано исключение: ${e.message}")
    }
    
    delay(1000)
    
    println("\n=== Пример с supervisorScope ===")
    supervisorScope {
        launch {
            delay(100)
            throw RuntimeException("Ошибка в supervisor!")
        }
        launch {
            delay(200)
            println("Эта корутина ВЫПОЛНИТСЯ несмотря на ошибку в соседней")
        }
    }
}

Обработка исключений

В SupervisorScope исключения нужно обрабатывать явно:

import kotlinx.coroutines.*

suspend fun handleExceptionsInSupervisor() {
    supervisorScope {
        val job1 = launch {
            try {
                // Код, который может выбросить исключение
                delay(100)
                throw RuntimeException("Что-то пошло не так")
            } catch (e: Exception) {
                println("Обработано исключение в дочерней корутине: ${e.message}")
            }
        }
        
        val job2 = async {
            // Для async нужно использовать await() с обработкой исключений
            delay(200)
            "Успешный результат"
        }
        
        try {
            val result = job2.await()
            println("Результат: $result")
        } catch (e: Exception) {
            println("Ошибка при получении результата: ${e.message}")
        }
    }
}

Типичные use-case

1. Параллельная загрузка данных

import kotlinx.coroutines.*

class DataLoader {
    suspend fun loadMultipleSources() = supervisorScope {
        val userData = async { loadUserData() }
        val newsFeed = async { loadNewsFeed() }
        val notifications = async { loadNotifications() }
        
        // Каждая задача выполняется независимо
        val results = listOf(userData, newsFeed, notifications)
            .mapNotNull { deferred ->
                try {
                    deferred.await()
                } catch (e: Exception) {
                    println("Ошибка загрузки: ${e.message}")
                    null
                }
            }
        
        results
    }
    
    private suspend fun loadUserData(): String {
        delay(500)
        return "User data"
    }
    
    private suspend fun loadNewsFeed(): String {
        delay(300)
        throw RuntimeException("Сервер новостей недоступен")
    }
    
    private suspend fun loadNotifications(): String {
        delay(400)
        return "Notifications"
    }
}

2. Обработка независимых UI операций

import kotlinx.coroutines.*
import kotlinx.coroutines.android.Main

class UserActivity {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    
    fun performIndependentTasks() {
        scope.launch {
            // Загрузка аватара
            launch {
                try {
                    loadUserAvatar()
                } catch (e: Exception) {
                    showPlaceholder()
                }
            }
            
            // Загрузка статистики
            launch {
                try {
                    loadUserStats()
                } catch (e: Exception) {
                    showDefaultStats()
                }
            }
            
            // Обновление баланса
            launch {
                try {
                    updateBalance()
                } catch (e: Exception) {
                    logBalanceError()
                }
            }
        }
    }
    
    private suspend fun loadUserAvatar() { /* ... */ }
    private fun showPlaceholder() { /* ... */ }
    private suspend fun loadUserStats() { /* ... */ }
    private fun showDefaultStats() { /* ... */ }
    private suspend fun updateBalance() { /* ... */ }
    private fun logBalanceError() { /* ... */ }
}

Важные предупреждения

1. Исключения не теряются

Хотя SupervisorScope не распространяет исключения автоматически, они не теряются. Необработанные исключения приводят к краху дочерней корутины:

import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        launch {
            // Это исключение не убьет родительскую корутину,
            // но дочерняя корутина будет завершена с ошибкой
            throw RuntimeException("Необработанное исключение")
        }
        
        delay(1000)
        println("Родительская корутина продолжает работу")
    }
}

2. Отмена SupervisorScope

import kotlinx.coroutines.*

fun demonstrateCancellation() = runBlocking {
    val supervisorJob = SupervisorJob()
    val scope = CoroutineScope(supervisorJob + Dispatchers.Default)
    
    scope.launch {
        repeat(10) { i ->
            println("Задача 1: шаг $i")
            delay(100)
        }
    }
    
    scope.launch {
        repeat(10) { i ->
            println("Задача 2: шаг $i")
            delay(100)
            if (i == 3) throw RuntimeException("Ошибка в задаче 2")
        }
    }
    
    delay(500)
    
    // Явная отмена всего scope
    scope.cancel()
    
    println("Все корутины отменены")
}

Best Practices

  1. Всегда обрабатывайте исключения в дочерних корутинах внутри SupervisorScope
  2. Используйте для независимых операций, которые не должны влиять друг на друга
  3. Не злоупотребляйте - если задачи логически связаны, возможно, обычный coroutineScope будет уместнее
  4. Комбинируйте с CoroutineExceptionHandler для централизованной обработки ошибок:
import kotlinx.coroutines.*

fun main() = runBlocking {
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Поймано исключение в handler: ${exception.message}")
    }
    
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + exceptionHandler)
    
    scope.launch {
        throw RuntimeException("Тестовое исключение")
    }
    
    delay(1000)
    scope.cancel()
}

Заключение

SupervisorScope — мощный инструмент в арсенале разработчика Kotlin Coroutines, который предоставляет гибкий контроль над обработкой исключений в параллельных операциях. Его основное преимущество — возможность создавать устойчивые системы, где сбой одного компонента не приводит к полному краху всей операции. Однако эта мощь требует ответственного использования и тщательной обработки исключений на уровне каждой дочерней корутины.

Что такое SupervisorScope? | PrepBro