Когда вы генерируете исключение в области сопрограмм, можно ли повторно использовать область сопрограмм?

У меня были проблемы с выяснением обработки ошибок с сопрограммами, которые я сузил до этого модульного теста со следующими шагами:

  1. Я создаю область сопрограммы с любым диспетчером.
  2. Я выбрасываю исключение в любом месте этой области в асинхронном блоке (или даже во вложенном асинхронном блоке).
  3. Я вызываю ожидание возвращенного отложенного значения и обрабатываю исключение.
  4. Это все хорошо. Однако, когда я пытаюсь использовать ту же область сопрограмм для запуска новой сопрограммы, это всегда завершается исключительно с тем же исключением.

    Вот тест:

    fun `when you throw an exception in a coroutine scope, is the coroutine scope dead?`() {
        val parentJob = Job()
        val coroutineScope = CoroutineScope(parentJob + Dispatchers.Default)
    
        val deferredResult = coroutineScope.async { throw IllegalStateException() }
    
        runBlocking {
            try {
                deferredResult.await()
            } catch (e: IllegalStateException) {
                println("We caught the exception. Good.")
            }
    
            try {
                coroutineScope.async { println("we can still use the scope") }.await()
            } catch (e: IllegalStateException) {
                println("Why is this same exception still being thrown?")
            }
    
        }
    
    }
    

Вот результат теста:

We caught the exception. Good.
Why is this same exception still being thrown?
  • Почему это происходит?

    • Насколько я понимаю, вы можете нормально обрабатывать исключения и восстанавливать их с помощью сопрограмм.
  • Как я должен иметь дело с исключениями?

    • Нужно ли создавать новый coroutineScope?
    • Могу ли я никогда не выдавать исключения, если я хочу продолжать использовать один и тот же coroutineScope?
    • Должен ли я вернуться Either<Result, Exception>?
    • Я пытался использовать CoroutineExceptionHandler, но я все еще получаю те же результаты.

Обратите внимание, что я использую Kotlin 1.3

1 ответ

Решение

Когда вы запускаете сопрограмму в области видимости (используя либо async или же launch), то сбой сопрограммы по умолчанию отменяет эту область, чтобы быстро отменить все другие дочерние элементы. Такой дизайн позволяет избежать висячих и потерянных исключений.

Общий совет здесь:

  • Не использовать async/await если вам действительно не нужен параллелизм. Когда вы разрабатываете свой код с функциями приостановки, нет особой необходимости использовать async а также await,

  • Если вам нужно параллельное выполнение, следуйте шаблону:

    coroutineScope { 
        val d1 = async { doOne() }
        val d2 = async { doTwo() }
        ...
        // retrieve and process results
        process(d1.await(), d2.await(), .... )
    }
    

Если вам нужно обработать сбой параллельной операции, то поставьте try { ... } catch { ... } вокруг coroutineScope { ... } поймать сбой в любой из одновременно выполняемых операций.

  • Есть дополнительные продвинутые механизмы (например, SupervisorJob), которые позволяют детализированную обработку исключений. Вы можете прочитать больше в документации https://kotlinlang.org/docs/reference/coroutines/exception-handling.html
Другие вопросы по тегам