CoroutineExceptionHandler не выполняется, когда предоставляется в качестве контекста запуска - PullRequest
0 голосов
/ 02 декабря 2018

Когда я запускаю это:

fun f() = runBlocking {
    val eh = CoroutineExceptionHandler { _, e -> trace("exception handler: $e") }
    val j1 = launch(eh) {
        trace("launched")
        delay(1000)
        throw RuntimeException("error!")
    }
    trace("joining")
    j1.join()
    trace("after join")
}
f()

Это вывод:

[main @coroutine#1]: joining
[main @coroutine#2]: launched
java.lang.RuntimeException: error!
    at ExceptionHandling$f9$1$j1$1.invokeSuspend(ExceptionHandling.kts:164)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)

В соответствии с документацией для CoroutineExceptionHandler , обработчик eh, который я предоставилдолжен быть выполнен.Но это не так.Почему это так?

Ответы [ 2 ]

0 голосов
/ 03 декабря 2018

Я полагаю, что ответ находится в этом разделе из официальных документов сопрограмм :

Если сопрограмма встречает исключение, отличное от CancellationException, она отменяет своего родителя с этим исключением.Это поведение не может быть переопределено и используется для обеспечения стабильных иерархий сопрограмм для структурированного параллелизма, которые не зависят от реализации CoroutineExceptionHandler.Исходное исключение обрабатывается родителем, когда завершаются все его дочерние элементы.

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

(выделение мое)

То, что здесь описано, относится не только к runBlocking и GlobalScope, но и к любому создателю сопрограмм не верхнего уровня и пользовательской области действия.

Для иллюстрации:

fun f() = runBlocking {
    val h1 = CoroutineExceptionHandler { _, e ->
        trace("handler 1 e: $e")
    }
    val h2 = CoroutineExceptionHandler { _, e ->
        trace("handler 2 e: $e")
    }
    val cs = CoroutineScope(newSingleThreadContext("t1"))
    trace("launching j1")
    val j1 = cs.launch(h1) {
        delay(1000)
        trace("launching j2")
        val j2 = launch(h2) {
            delay(500)
            trace("throwing exception")
            throw RuntimeException("error!")
        }
        j2.join()
    }
    trace("joining j1")
    j1.join()
    trace("exiting f")
}
f()

Вывод:

[main @coroutine#1]: launching j1
[main @coroutine#1]: joining j1
[t1 @coroutine#2]: launching j2
[t1 @coroutine#3]: throwing exception
[t1 @coroutine#2]: handler 1 e: java.lang.RuntimeException: error!
[main @coroutine#1]: exiting f

Обратите внимание, что обработчик h1 выполнен, а h2 - нет.Это аналог обработчика при выполнении GlobalScope#launch, но не обработчик, предоставленный любому launch внутри runBlocking.

TLDR

Обработчики, предоставленные некорневым сопрограммамиз области будет игнорироваться.Будет выполнен обработчик, предоставленный для корневой сопрограммы.

Как правильно указал Марко Топольник в комментариях ниже, приведенное выше обобщение применимо только к сопрограммам, созданным launch.Созданные async или produce всегда будут игнорировать все обработчики.

0 голосов
/ 02 декабря 2018

Какая у вас kotlinx.coroutines версия?Начиная с 0.26.0, автономный launch строитель теперь устарел, и вы должны использовать GlobalScope.launch.

Я попробовал ваш образец, и после этого изменения он заработал.

Kotlinx.coroutines changelog

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...