Котлин сопрограмма не может справиться с исключением - PullRequest
0 голосов
/ 10 ноября 2018

Я играл с сопрограммами и обнаружил очень странное поведение. Я хочу преобразовать некоторые асинхронные запросы в моем проекте, используя suspendCoroutine(). Вот фрагмент кода, показывающий эту проблему.

В первом случае, когда функция приостановки вызывается в сопрограмме runBlocking, исключение из продолжения переходит к блоку перехвата, а затем runBlocking успешно завершается. Но во втором случае, когда создается новая сопрограмма async, исключение проходит через блок catch и вызывает сбой всей программы.

package com.example.lib

import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

object Test {
    fun runSuccessfulCoroutine() {
        runBlocking {
            try {
                Repository.fail()
            } catch (ex: Throwable) {
                println("Catching ex in runSuccessfulCoroutine(): $ex")
            }
        }
    }

    fun runFailingCoroutine() {
        runBlocking {
            try {
                async { Repository.fail() }.await()
            } catch (ex: Throwable) {
                println("Catching ex in runFailingCoroutine(): $ex")
            }
        }
    }
}

object Repository {
    suspend fun fail(): Int = suspendCoroutine { cont ->
        cont.resumeWithException(RuntimeException("Exception at ${Thread.currentThread().name}"))
    }
}


fun main() {
    Test.runSuccessfulCoroutine()
    println()

    Test.runFailingCoroutine()

    println("We will never get here")
}

Вот что напечатано на консоли:

Catching ex in runSuccessfulCoroutine(): java.lang.RuntimeException: Exception at main

Catching ex in runFailingCoroutine(): java.lang.RuntimeException: Exception at main
Exception in thread "main" java.lang.RuntimeException: Exception at main
    at com.example.lib.Repository.fail(MyClass.kt:32)
    at com.example.lib.Test$runFailingCoroutine$1$1.invokeSuspend(MyClass.kt:22)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
    at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:69)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at com.example.lib.Test.runFailingCoroutine(MyClass.kt:20)
    at com.example.lib.MyClassKt.main(MyClass.kt:41)
    at com.example.lib.MyClassKt.main(MyClass.kt)

Process finished with exit code 1

Любые идеи, почему это происходит - это ошибка, или я неправильно использую сопрограммы?

Обновление:

Использование coroutineScope { ... } уменьшит проблему в runFailingCoroutine()

fun runFailingCoroutine() = runBlocking {
    try {
        coroutineScope { async { fail() }.await()  }
    } catch (ex: Throwable) {
        println("Catching ex in runFailingCoroutine(): $ex")
    }
}

Ответы [ 2 ]

0 голосов
/ 10 ноября 2018

Я был поражен этим поведением только вчера, вот мой анализ .

В двух словах, такое поведение желательно, потому что async не имеет той же цели, что и в других языках. В Kotlin вы должны использовать его экономно, только когда вам нужно разбить задачу на несколько подзадач, которые выполняются параллельно.

Всякий раз, когда вы просто хотите написать

val result = async { work() }.await()

вы должны вместо этого написать

val result = withContext(Default) { work() }

и это будет вести себя ожидаемым образом. Кроме того, всякий раз, когда у вас есть такая возможность, вы должны переместить вызов withContext в функцию work() и сделать его suspend fun.

0 голосов
/ 10 ноября 2018

Поведение вашего второго примера правильное, это работа структурированного параллелизма. Поскольку внутренний блок async вызывает исключение, эта сопрограмма отменяется. Из-за структурированного параллелизма родительская работа также отменяется.

Посмотрите на этот маленький пример:

val result = coroutineScope {
    async {
        throw IllegalStateException()
    }
    10
}

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

Если вам не нравится это поведение, вы можете использовать supervisorScope. В этом случае внутренняя сопрограмма может выйти из строя, не нарушив внешнюю сопрограмму.

val result = supervisorScope {
    async {
        throw IllegalStateException()
    }
    10
}

В вашем первом примере вы ловите исключение внутри блока сопрограммы, потому что сопрограмма завершается нормально.

Для обсуждения этой темы см .:

...