首页 > 解决方案 > 协程 CancellationException 预期行为

问题描述

因此,基于 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.

finally 块没有执行可能是由于在那里运行挂起函数,但我希望在CancellationException我运行该代码时到达那里:

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

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

标签: androidkotlinkotlin-coroutines

解决方案


异常是从那个点抛出的doWorld(),从那个点开始,它会逃脱协程块并被默默吞下,因为您没有安装任何未处理的异常处理程序。它正在运行的调度程序不会由于该异常而崩溃。

如果您研究上述链接下的文档,您将了解到异常被默默吞下只是因为它是一个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线程实际上并没有死:它继续打印 line runBlocking done。Kotlin 只是重用已安装的文件currentThread().uncaughtExceptionHandler()来记录协程失败。


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

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

我为此创建了一个问题


推荐阅读