Что такое SupervisorScope?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# 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
}
}
Ключевые особенности:
- Независимость дочерних корутин - ошибка в одной дочерней корутине не приводит к отмене других дочерних корутин
- Родительская корутина продолжает работу даже при сбое дочерних
- Исключения не распространяются автоматически на родительскую корутину
- Требуется явная обработка исключений в дочерних корутинах
Практическое использование
Создание 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
- Всегда обрабатывайте исключения в дочерних корутинах внутри
SupervisorScope - Используйте для независимых операций, которые не должны влиять друг на друга
- Не злоупотребляйте - если задачи логически связаны, возможно, обычный
coroutineScopeбудет уместнее - Комбинируйте с 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, который предоставляет гибкий контроль над обработкой исключений в параллельных операциях. Его основное преимущество — возможность создавать устойчивые системы, где сбой одного компонента не приводит к полному краху всей операции. Однако эта мощь требует ответственного использования и тщательной обработки исключений на уровне каждой дочерней корутины.