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

Как можно повторить поведение SupervisorJob без его использования?

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

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

SupervisorJob — поведение без его использования

SupervisorJob — это специальный Job, который изолирует отказы дочерних корутин. Если одна дочерняя корутина выбросит исключение, это не приведет к отмене всех остальных. Можно повторить это поведение без SupervisorJob несколькими способами.

Что делает SupervisorJob

// С SupervisorJob — одна ошибка не отменяет других
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

scope.launch { throw Exception("Error 1") }  // Упадет
scope.launch { println("Work 2") }           // Выполнится!
scope.launch { println("Work 3") }           // Выполнится!

// Без SupervisorJob — ошибка отменит все
val scope = CoroutineScope(Job() + Dispatchers.Main)

scope.launch { throw Exception("Error 1") }  // Упадет
scope.launch { println("Work 2") }           // Отменится!
scope.launch { println("Work 3") }           // Отменится!

Способ 1: Обработка исключений внутри launch

val scope = CoroutineScope(Job() + Dispatchers.Main)

scope.launch {
    try {
        // Первая задача
        throw Exception("Error in task 1")
    } catch (e: Exception) {
        Log.e("TAG", "Task 1 failed", e)
    }
}

scope.launch {
    // Вторая задача выполнится несмотря на ошибку в первой
    println("Task 2 completed")
}

scope.launch {
    // Третья задача тоже выполнится
    println("Task 3 completed")
}

Способ 2: CoroutineExceptionHandler

val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    Log.e("TAG", "Caught exception: ${exception.message}")
}

val scope = CoroutineScope(Job() + Dispatchers.Main + exceptionHandler)

scope.launch {
    throw Exception("Error 1")
}

scope.launch {
    println("Work 2")
}

scope.launch {
    println("Work 3")
}

// Важно: CoroutineExceptionHandler работает только для launch {},
// а не для async {} — это ключевое отличие!

Способ 3: Оборачивание каждой задачи в try-catch

val scope = CoroutineScope(Job() + Dispatchers.Main)

fun safeTask(block: suspend () -> Unit) {
    scope.launch {
        try {
            block()
        } catch (e: Exception) {
            Log.e("TAG", "Task failed: ${e.message}")
        }
    }
}

safeTask {
    throw Exception("Error 1")
}

safeTask {
    println("Work 2")
}

safeTask {
    println("Work 3")
}

Способ 4: Эмуляция SupervisorJob через Job + Exception Handling

class SupervisorJobEmulator {
    private val jobs = mutableListOf<Job>()
    
    fun launchSafely(block: suspend () -> Unit) {
        val job = Job()
        jobs.add(job)
        
        CoroutineScope(job + Dispatchers.Main).launch {
            try {
                block()
            } catch (e: Exception) {
                Log.e("TAG", "Task failed: ${e.message}")
            }
        }
    }
    
    suspend fun joinAll() {
        jobs.forEach { it.join() }
    }
    
    fun cancelAll() {
        jobs.forEach { it.cancel() }
    }
}

// Использование
val supervisor = SupervisorJobEmulator()

supervisor.launchSafely {
    throw Exception("Error")
}

supervisor.launchSafely {
    println("Work 2")
}

supervisor.launchSafely {
    println("Work 3")
}

Способ 5: Функция-расширение для scopedLaunch

fun CoroutineScope.safeLaunch(block: suspend () -> Unit): Job {
    return launch {
        try {
            block()
        } catch (e: Exception) {
            Log.e("TAG", "Coroutine failed: ${e.message}")
        }
    }
}

// Использование
val scope = CoroutineScope(Job() + Dispatchers.Main)

scope.safeLaunch {
    throw Exception("Error")
}

scope.safeLaunch {
    println("Work 2")
}

scope.safeLaunch {
    println("Work 3")
}

Способ 6: Через async с обработкой исключений

val scope = CoroutineScope(Job() + Dispatchers.Main)

val deferred1 = scope.async {
    try {
        throw Exception("Error")
    } catch (e: Exception) {
        null
    }
}

val deferred2 = scope.async {
    println("Work 2")
}

val deferred3 = scope.async {
    println("Work 3")
}

// Все три будут выполнены независимо от ошибки в первой
scope.launch {
    val results = awaitAll(deferred1, deferred2, deferred3)
}

Практический пример: загрузка нескольких данных

class UserRepository {
    private val scope = CoroutineScope(Job() + Dispatchers.IO)
    
    fun loadMultipleUsers(userIds: List<Int>) {
        userIds.forEach { userId ->
            scope.launch {
                try {
                    val user = fetchUser(userId)
                    println("Loaded: $user")
                } catch (e: Exception) {
                    println("Failed to load user $userId: ${e.message}")
                    // Другие пользователи будут загружены несмотря на ошибку
                }
            }
        }
    }
    
    private suspend fun fetchUser(id: Int): String {
        delay(1000)
        if (id == 2) throw Exception("Network error")
        return "User $id"
    }
}

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

ПодходПростотаКонтрольРекомендация
SupervisorJob⭐⭐⭐⭐⭐ОтличныйИспользуй это!
try-catch в launch⭐⭐⭐ХорошийДля простых случаев
CoroutineExceptionHandler⭐⭐⭐⭐ХорошийЦентрализованная обработка
Функция-расширение⭐⭐⭐⭐ОтличныйПереиспользуемо

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

SupervisorJob специально разработан для этой задачи, поэтому его лучше использовать, чем эмулировать

try-catch работает для простых случаев, но код становится многословным

CoroutineExceptionHandler подходит для централизованной обработки, но только для launch

async требует обработки через runCatching() или try-catch, иначе исключение будет выброшено при await()

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