Как можно поймать исключение в корутинах?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка исключений в Kotlin Coroutines
Обработка исключений в корутинах — критически важный аспект для создания стабильных Android-приложений. В отличие от традиционных потоков, корутины предоставляют несколько механизмов обработки ошибок, каждый со своей семантикой.
Основные подходы к обработке исключений
1. try-catch внутри корутины
Стандартный подход, работающий аналогично обычному коду:
viewModelScope.launch {
try {
val result = repository.fetchData() // Может выбросить исключение
processResult(result)
} catch (e: IOException) {
// Обработка сетевых ошибок
showError("Network error: ${e.message}")
} catch (e: Exception) {
// Общая обработка
showError("Unexpected error")
}
}
2. CoroutineExceptionHandler для корутин верхнего уровня
Глобальный обработчик для корутин, запущенных через launch:
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
// Логирование или уведомление об ошибке
Log.e("CoroutineError", "Unhandled exception", throwable)
showGlobalError(throwable)
}
// Использование с launch
viewModelScope.launch(exceptionHandler) {
throw IOException("Network failure")
}
Важно: CoroutineExceptionHandler работает только с корутинами, запущенными через launch, а не через async.
3. Обработка исключений в async/await
Для конструкций async исключения откладываются до вызова await():
viewModelScope.launch {
val deferred = async {
fetchData() // Может выбросить исключение
}
try {
val result = deferred.await()
processResult(result)
} catch (e: Exception) {
// Обработка исключения из async
handleError(e)
}
}
SupervisorJob и SupervisorScope
Обычный Job отменяет все дочерние корутины при ошибке в одной из них. SupervisorJob меняет это поведение:
// С SupervisorJob ошибка в одной корутине не отменяет другие
val supervisorJob = SupervisorJob()
val scope = CoroutineScope(Dispatchers.Main + supervisorJob)
scope.launch {
// Эта корутина упадет
throw RuntimeException("Error 1")
}
scope.launch {
// Эта продолжит работу даже после падения предыдущей
delay(1000)
println("I'm still alive!")
}
SupervisorScope предоставляет то же поведение в виде области видимости:
supervisorScope {
launch {
// Не зависит от других корутин в этом scope
throw IOException()
}
launch {
// Будет продолжено выполнение
delay(1000)
fetchData()
}
}
Особенности обработки исключений
Отмена корутин
- При отмене корутина выбрасывает
CancellationException - Это исключение игнорируется обработчиками и не логируется
- Для реагирования на отмену используйте
finallyили проверкуisActive
job = launch {
try {
longRunningOperation()
} finally {
// Выполнится даже при отмене
cleanupResources()
if (isActive) {
// Только если не была отменена
updateUI()
}
}
}
Комбинирование подходов
На практике часто используются комбинации:
val handler = CoroutineExceptionHandler { _, e ->
analytics.logError(e)
}
viewModelScope.launch(handler) {
supervisorScope {
val userData = async { fetchUserData() }
val settings = async { fetchSettings() }
try {
val combinedData = combineData(userData.await(), settings.await())
updateUI(combinedData)
} catch (e: CompositeException) {
// Обработка частичных сбоев
showPartialData(e)
}
}
}
Рекомендации для Android-разработки
- Используйте SupervisorJob для независимых операций (например, несколько параллельных запросов к разным API)
- Логируйте необработанные исключения через
CoroutineExceptionHandlerдля отладки - Разделяйте ответственность: бизнес-логика должна пробрасывать исключения, UI-слой — обрабатывать
- Тестируйте сценарии с ошибками с помощью
TestCoroutineDispatcher - Для глобальной обработки создайте базовый класс ViewModel с общим обработчиком ошибок
Правильная обработка исключений в корутинах делает приложение устойчивым к сбоям и улучшает пользовательский опыт, позволяя грациозно восстанавливаться после ошибок.