【问题标题】:Coroutines CancellationException expected behaviour协程 CancellationException 预期行为
【发布时间】:2023-03-03 18:13:02
【问题描述】:

所以基于 Kotlin 对协程的介绍,在 Cancellation and Timeouts -> Run non-cancellable block 我找到了以下解释:Any attempt to use a suspending function in the finally block [...] causes CancellationException 但是在运行时:

fun runPlayground() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                Log.d("XXX", "job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            doWorld() // logs "World" after 1s delay
            delay(2000L)
            Log.d("XXX", "job: I'm running finally")
        }
    }
    delay(1300L) // delay a bit
    Log.d("XXX", "main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    Log.d("XXX", "main: Now I can quit.")
}

记录结果:

D/XXX: job: I'm sleeping 0 ...
D/XXX: job: I'm sleeping 1 ...
D/XXX: job: I'm sleeping 2 ...
D/XXX: main: I'm tired of waiting!
D/XXX: main: Now I can quit.

最终块没有被执行可能是因为在那里运行挂起函数,但我希望在我从以下位置运行代码时得到CancellationException

try {
    runPlayground()
} catch (e: CancellationException)  {
    Log.d("XXX", e.message)
}

异常是否在协程内部处理?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    异常是从doWorld() 抛出的,从那时起,它会逃脱协程块并被静默吞没,因为您还没有安装任何未处理的exception handler。运行它的调度程序不会因为该异常而崩溃。

    如果您研究上述链接下的文档,您会发现异常被默默吞下只是因为它是CancelationException。任何其他异常至少会以与 Java 线程中出现未处理异常相同的方式出现,例如:

    fun main() = runBlocking {
        launch(Job()) {
            throw Exception("I failed")
        }.join()
        println("runBlocking done")
    }
    

    打印出来

    Exception in thread "main" java.lang.Exception: I failed
        at org.mtopol.TestingKt$main$1$1.invokeSuspend(testing.kt:8)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:270)
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
        at org.mtopol.TestingKt.main(testing.kt:6)
        at org.mtopol.TestingKt.main(testing.kt)
    
    runBlocking done
    

    特别注意main 线程实际上并没有死:它继续打印runBlocking done 行。 Kotlin 只是重用已安装的 currentThread().uncaughtExceptionHandler() 来记录协程失败。


    当我去测试类似于上面的代码,但安装了CoroutineExceptionHandler时,我发现了一些问题:

    1. 子协程中的异常处理程序被忽略。这似乎是设计使然,但没有记录。
    2. runBlocking 似乎有一个错误,即使您为其安装处理程序,它也不会运行。

    我为此创建了一个issue

    【讨论】:

    • 有没有可能以某种方式捕捉到它?据我所知,在将 EH 添加到启动或 runBlocking 之后,我仍然没有收到 CancellationException 的日志,当抛出任何其他 RuntimeException 时,我以应用程序崩溃结束(在 Android 应用程序的 onCreate 中运行它)。我尝试使用上下文进行试验,但结果仍然相同。
    • CoroutineExceptionHandler 实际上并没有处理异常,它只是观察它。到那时协程已经崩溃了。如果你想优雅地处理协程异常,你必须在协程构建器块(协程的顶层)内直接有一个try-catch
    • 但是当 EH 只记录异常消息时,我可以期望他这样做吗?在我的情况下没有发生这种情况。现在 EH 似乎什么也没做,它没有被调用,但后来崩溃/由外部 try catch 处理的异常。
    • 顺便说一句。我知道这个用例可能甚至不是真实的,但我正在试验并发现更深入地调查/理解这很有趣。
    • 我建议您研究CoroutineExceptionHandler 上的文档,它准确地说明了处理和/或报告的内容和时间。
    猜你喜欢
    • 2021-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-13
    • 2011-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多