Какие способы обработки ошибок в корутинах вы знаете?

CoroutineExceptionHandler срабатывает только для исключений, которые не обрабатываются механизмом структурированной конкурентности.

Короткий ответ

Что ответить на собеседовании

CoroutineExceptionHandler срабатывает только для исключений, которые не обрабатываются механизмом структурированной конкурентности. SupervisorJob и supervisorScope. В отличие от обычного Job, когда дочерняя корутина с ошибкой приводит к отмене родительского Job, SupervisorJob не отменяет родительский Job при ошибке дочерней корутины. supervisorScope создает CoroutineScope с SupervisorJob.

Подробный разбор

Ответ с пояснениями

  • Использование try...catch блоков. Стандартный способ обработки исключений. Работает внутри корутины.

    // Применение try-catch
    suspend fun fetchData(): String {
        return try {
            // Операция, которая может выбросить исключение
            throw Exception("Ошибка загрузки данных")
            "Данные успешно загружены"
        } catch (e: Exception) {
            "Ошибка: ${e.message}"
        }
    }
    

    try...catch не подходит для обработки uncaught исключений, выброшенных из дочерних корутин, запущенных в другом CoroutineScope.

  • CoroutineExceptionHandler. Перехватывает необработанные исключения, выброшенные корутинами, которые запущены в данном CoroutineScope или в его дочерних CoroutineScope.

    // CoroutineExceptionHandler
    val handler = CoroutineExceptionHandler { _, exception ->
        println("Перехвачено исключение: $exception")
    }
    
    // Применение CoroutineExceptionHandler
    GlobalScope.launch(handler) {
        // Корутина, которая может выбросить исключение
        throw Exception("Ошибка в корутине")
    }
    

    CoroutineExceptionHandler срабатывает только для исключений, которые не обрабатываются механизмом структурированной конкурентности.

  • SupervisorJob и supervisorScope. В отличие от обычного Job, когда дочерняя корутина с ошибкой приводит к отмене родительского Job, SupervisorJob не отменяет родительский Job при ошибке дочерней корутины. supervisorScope создает CoroutineScope с SupervisorJob.

    // Использование supervisorScope
    suspend fun loadMultipleData() = supervisorScope {
        val data1 = async {
            // Может выбросить исключение
            throw Exception("Ошибка в данных 1")
            "Данные 1"
        }
        val data2 = async {
            "Данные 2"
        }
    
        // Можно обработать исключение для конкретной async
        try {
            println("Результат 1: ${data1.await()}")
        } catch (e: Exception) {
            println("Ошибка при загрузке данных 1: ${e.message}")
        }
        println("Результат 2: ${data2.await()}")
    }
    

    Используется для независимых дочерних задач, где ошибка одной не должна влиять на другие. Не заменяет try...catch или CoroutineExceptionHandler, скорее меняет поведение распространения исключений.

  • Распространение исключений в структурированной конкурентности. По умолчанию, исключение в дочерней корутине отменяет родительскую корутину и всех ее сиблингов.

    // Пример распространения исключения
    runBlocking {
        val job = launch {
            launch { // Дочерняя корутина 1
                delay(100)
                throw Exception("Ошибка в дочерней 1")
            }
            launch { // Дочерняя корутина 2
                delay(200)
                println("Дочерняя 2 выполнилась")
            }
        }
        try {
            job.join()
        } catch (e: Exception) {
            println("Исключение перехвачено в родительской: $e")
        }
    }
    

    Это базовое поведение структурированной конкурентности, которое помогает избежать утечек ресурсов и состояний.

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

    // Обработка исключений с async/await
    suspend fun safeAsyncCall() = coroutineScope {
        val deferred = async {
            throw Exception("Ошибка в async")
            "Результат async"
        }
    
        try {
            val result = deferred.await()
            println("Результат: $result")
        } catch (e: Exception) {
            println("Перехвачено исключение из async: ${e.message}")
        }
    }
    

    Это позволяет выборочно обрабатывать ошибки для каждой асинхронной задачи.

Способ Назначение Обрабатывает uncaught? Изменяет распространение?
try...catch Обработка исключений внутри текущей корутины Нет Нет
CoroutineExceptionHandler Перехват необработанных исключений в Scope Да Нет
SupervisorJob/Scope Изменение поведения распространения исключений Нет Да
Структурированная конкурентность Автоматическая отмена при ошибке Да Да
async + await() Отложенное распространение исключения Нет Да (до await)

Практика в реальном времени

Подготовьтесь к следующему собеседованию

Interview Boost учитывает вакансию, резюме и технологии и помогает сформулировать ответ прямо во время интервью.

Начать подготовку