Как обработать ошибку в корутине с помощью try/catch
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка ошибок в корутинах через try/catch
В Kotlin корутинах обработка ошибок с помощью try/catch работает, но с важными нюансами, которые отличаются от обычного синхронного кода. Основная сложность заключается в иерархии исключений и структуре корутин.
Базовый пример обработки
fun main() = runBlocking {
try {
launch {
// Этот try/catch НЕ поймает исключение из дочерней корутины!
throw RuntimeException("Ошибка в дочерней корутине")
}
} catch (e: Exception) {
println("Это не сработает: ${e.message}")
}
delay(1000)
}
Ключевые принципы обработки ошибок
1. Локальный try/catch внутри корутины
Наиболее надежный способ — обрабатывать ошибки непосредственно внутри корутины:
launch {
try {
riskyOperation()
} catch (e: Exception) {
println("Ошибка обработана локально: ${e.message}")
}
}
suspend fun riskyOperation() {
delay(100)
throw IOException("Сетевая ошибка")
}
2. Использование CoroutineExceptionHandler
Для обработки неотловленных исключений в корутинах высшего уровня:
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught unhandled exception: $exception")
}
fun main() = runBlocking {
val job = launch(handler) {
throw RuntimeException("Необработанная ошибка")
}
job.join()
}
Важные особенности поведения
Структурная конкурентность и отмена
Когда исключение выбрасывается в корутине:
- Родительская корутина получает уведомление об исключении
- Все дочерние корутины отменяются
- Родительская корутина также отменяется (если не обработано)
fun main() = runBlocking {
val parentJob = launch {
val child1 = launch {
delay(Long.MAX_VALUE)
}
val child2 = launch {
delay(100)
throw RuntimeException("Ошибка в child2")
}
}
parentJob.join()
println("Родительская корутина была отменена")
}
SupervisorJob для независимого поведения
Используйте SupervisorJob, когда нужно, чтобы ошибка в одной корутине не влияла на другие:
fun main() = runBlocking {
val supervisorScope = CoroutineScope(SupervisorJob())
supervisorScope.launch {
delay(100)
throw RuntimeException("Ошибка №1")
}
supervisorScope.launch {
delay(200)
println("Эта корутина продолжит работу")
}
delay(300)
}
async/await с try/catch
При работе с async корутинами обработка отличается:
fun main() = runBlocking {
val deferred = async {
// Исключение здесь будет отложено
throw RuntimeException("Ошибка в async")
}
try {
deferred.await() // Исключение выбросится здесь при вызове await()
} catch (e: Exception) {
println("Ошибка поймана при await: ${e.message}")
}
}
Практические рекомендации
- Всегда обрабатывайте ошибки на том уровне, где их можно осмысленно обработать
- Используйте CoroutineExceptionHandler для корутин верхнего уровня (например, в Android это может быть
lifecycleScopeилиviewModelScope) - Для независимых операций применяйте SupervisorJob
- Помните про различия между launch и async:
launch: исключения сразу отменяют корутинуasync: исключения откладываются до вызоваawait()
Пример комплексной обработки
class SafeCoroutineExecutor {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
// Логирование в Crashlytics/Analytics
logError(throwable)
}
private val scope = CoroutineScope(
SupervisorJob() + Dispatchers.IO + exceptionHandler
)
fun executeSafeOperation() {
scope.launch {
try {
val data = fetchData()
processData(data)
} catch (e: NetworkException) {
showUserMessage("Проблемы с сетью")
} catch (e: ProcessingException) {
retryOperation()
}
}
}
private suspend fun fetchData(): String {
// Сетевая операция, которая может выбросить исключение
return ""
}
}
Главное правило: try/catch эффективен только для синхронного кода и suspend функций внутри корутины. Для асинхронных исключений используйте комбинацию локальных обработчиков, CoroutineExceptionHandler и правильной структуры корутин через SupervisorJob.