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

Как обрабатывать исключения корутин в Kotlin?

1.0 Junior🔥 282 комментариев
#Kotlin основы

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

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

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

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

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

Основные подходы к обработке

1. Try-Catch внутри корутины

Самый простой способ — обрабатывать исключения непосредственно в блоке кода корутины:

viewModelScope.launch {
    try {
        val result = repository.fetchData() // Может выбросить исключение
        updateUi(result)
    } catch (e: IOException) {
        showError("Ошибка сети: ${e.message}")
    } catch (e: IllegalArgumentException) {
        showError("Неверные данные: ${e.message}")
    }
}

2. CoroutineExceptionHandler для корутин верхнего уровня

Для обработки непойманных исключений в корутинах верхнего уровня используйте CoroutineExceptionHandler:

val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
    println("Необработанное исключение: ${throwable.message}")
    // Отправляем в Crashlytics/аналитику
    FirebaseCrashlytics.getInstance().recordException(throwable)
}

// Использование с launch
viewModelScope.launch(exceptionHandler) {
    throw IOException("Сетевая ошибка")
}

Важно: CoroutineExceptionHandler работает только с корутинами, запущенными через launch, а не async.

3. Обработка в async/await конструкциях

Для async корутин исключения обрабатываются при вызове await():

viewModelScope.launch {
    val deferred = async {
        repository.fetchData() // Может выбросить исключение
    }
    
    try {
        val result = deferred.await()
        updateUi(result)
    } catch (e: Exception) {
        handleError(e)
    }
}

Ключевые особенности и лучшие практики

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

  • Исключения, не обработанные внутри корутины, автоматически распространяются вверх по иерархии
  • Родительская корутина отменяется при необработанном исключении в дочерней (по умолчанию)
  • Все дочерние корутины также отменяются

SupervisorJob для изолированной обработки

Используйте SupervisorJob или supervisorScope, когда нужно предотвратить отмену родительской корутины при исключении в дочерней:

// С SupervisorJob - исключения в дочерних корутинах не отменяют родителя
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Main + supervisorJob)

scope.launch {
    // Эта корутина выполнится, даже если вторая упадет
    launch { fetchUserData() }
    launch { 
        // Исключение здесь не отменит первую корутину
        throw IOException("Ошибка в параллельной задаче") 
    }
}

// Или с supervisorScope
viewModelScope.launch {
    supervisorScope {
        launch { fetchData1() }
        launch { fetchData2() } // Исключение здесь не затронет fetchData1()
    }
}

Различия между launch и async

  • launch: Исключения немедленно выбрасываются и могут быть обработаны CoroutineExceptionHandler
  • async: Исключения откладываются до вызова await() и должны быть обработаны там

Паттерны для Android-разработки

ViewModel с обработкой состояния

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState
    
    fun loadData() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val data = repository.loadData()
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
                // Логирование для аналитики
                logException(e)
            }
        }
    }
}

Global Exception Handler

Создайте глобальный обработчик для всего приложения:

object GlobalCoroutineExceptionHandler {
    private val handler = CoroutineExceptionHandler { context, exception ->
        // Логирование
        Timber.e(exception, "Global coroutine exception")
        
        // Отправка в аналитику
        FirebaseCrashlytics.getInstance().recordException(exception)
        
        // Показ уведомления пользователю (если нужно)
        if (exception is NetworkException) {
            showGlobalErrorNotification()
        }
    }
    
    fun getHandler() = handler
}

Рекомендации по обработке конкретных типов исключений

  1. Сетевые ошибки — используйте retry-механизмы с экспоненциальной задержкой
  2. Ошибки валидации — показывайте пользователю понятные сообщения
  3. Критические ошибки — логируйте и отправляйте в системы мониторинга
  4. Отмена корутин — обрабатывайте CancellationException отдельно, так как это нормальный механизм завершения

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