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

Как обработать ошибку в корутине с помощью try/catch

1.0 Junior🔥 242 комментариев
#Kotlin основы#Многопоточность и асинхронность

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

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

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

Обработка ошибок в корутинах через 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}")
    }
}

Практические рекомендации

  1. Всегда обрабатывайте ошибки на том уровне, где их можно осмысленно обработать
  2. Используйте CoroutineExceptionHandler для корутин верхнего уровня (например, в Android это может быть lifecycleScope или viewModelScope)
  3. Для независимых операций применяйте SupervisorJob
  4. Помните про различия между 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.