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

Как отлавливать ошибки в CoroutineScope

2.3 Middle🔥 221 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

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

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

Обработка ошибок в CoroutineScope в Kotlin Coroutines

Обработка ошибок в CoroutineScope — критически важный навык для разработчика Android, использующего Kotlin Coroutines. Неправильная обработка может привести к незаметным падениям приложений и нестабильности. Существует несколько ключевых подходов, зависящих от типа CoroutineScope, контекста и желаемого поведения.

Основные принципы и различия в Scope

Прежде всего, нужно понимать разницу между CoroutineScope как структурой для запуска корутин и CoroutineContext, в котором находится Job и CoroutineExceptionHandler. Важнейшее разделение:

  • Супервизор-джоб (SupervisorJob): Позволяет независимо работать корутинам внутри Scope. Ошибка в одной корутине не cancels другие.
  • Обычный джоб (Job): Ошибка в одной корутине распространяется на весь Scope и приводит к cancel всех других корутин.
// Scope с SupervisorJob - ошибки локализованы
val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

// Scope с обычным Job - ошибки каскадные
val regularScope = CoroutineScope(Job() + Dispatchers.IO)

Практические методы отлова ошибок

1. Использование try-catch внутри корутины

Базовый и самый прямой способ для ожидаемых исключений внутри suspend функций или блоков launch.

supervisorScope.launch {
    try {
        riskySuspendFunction()
    } catch (e: IOException) {
        // Логируем или обрабатываем специфичную ошибку
        logError("Network failed", e)
    }
}

Важно: try-catch не отлавливает исключения из async блоков при вызове await(). Ошибка возникает именно в точке await().

2. CoroutineExceptionHandler для корутин, запущенных через launch

CoroutineExceptionHandler — специальный элемент контекста, который глобально обрабатывает неотловленные исключения в корутинах, запущенных через launch. Он не работает для корутин, запущенных через async (там ошибки отлавливаются через await()).

val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    // Централизованная обработка критических ошибок
    println("Caught unhandled exception in launch: ${exception}")
    // Можно отправлять в Crashlytics или показывать уведомление
}

val scopeWithHandler = CoroutineScope(SupervisorJob() + exceptionHandler + Dispatchers.Main)

scopeWithHandler.launch {
    throw RuntimeException("Test crash in launch")
    // Исключение будет передано в exceptionHandler, а не приведет к немедленному падению
}

3. Обработка ошибок в async корутинах

Для корутин, запущенных через async, исключения отлавливаются при вызове await(). Ошибка "хранится" внутри Deferred до момента получения результата.

supervisorScope.launch {
    val deferredResult = supervisorScope.async {
        throw IOException("Async operation failed")
    }

    try {
        deferredResult.await()
    } catch (e: IOException) {
        // Обработка ошибки из async блока
        println("Async error caught: ${e.message}")
    }
}

4. Комбинированный подход в Android Lifecycle Scope

В Android часто используются viewModelScope или lifecycleScope, которые по умолчанию используют SupervisorJob(). Рекомендуемая стратегия:

  • Для операций с результатом (например, загрузка данных) используйте async и try-catch на await().
  • Для фоновых операций без прямого результата, где ошибка должна быть залогирована, добавьте CoroutineExceptionHandler в контекст.
  • Для критических операций, где ошибка должна быть показана пользователю, используйте try-catch внутри корутины и соответствующую UI-обработку.
// Пример в ViewModel с обработкой ошибки и UI state
viewModelScope.launch {
    try {
        val data = repository.loadData() // suspend функция
        _uiState.value = UiState.Success(data)
    } catch (e: Exception) {
        _uiState.value = UiState.Error(e.message ?: "Unknown error")
        // Также можно залогировать в аналитику
    }
}

Продвинутые сценарии и предостережения

  • Необработанные исключения в launch без CoroutineExceptionHandler: В Scope с обычным Job приводят к cancel всего Scope. В Scope с SupervisorJob — только к cancel конкретной корутины, но исключение может "потеряться" если не залогировано.
  • Исключения в корутинах, запущенных в GlobalScope: Обрабатываются через CoroutineExceptionHandler, добавленный в контекст запуска, но использование GlobalScope в Android обычно не рекомендуется.
  • Ошибки в withContext: Отлавливаются стандартным try-catch внутри блока.

Ключевой вывод: Выбор стратегии зависит от требуемого поведения — локализация ошибки vs. каскадное падение, необходимость централизованного логирования vs. локальной UI-обработки. Правильная комбинация SupervisorJob, CoroutineExceptionHandler и try-catch обеспечивает надежную и понятную обработку ошибок в корутинах на Android.

Как отлавливать ошибки в CoroutineScope | PrepBro