kotlin中是否有必要重新抛出CancellationException

Ven*_*cat 3 kotlin kotlin-coroutines

我正在阅读《Kotlin 协程深度探究》一书,以进一步了解协程。我在书中发现了以下陈述,我无法清楚地理解,如果有人用一个简单的例子来解释我将会有所帮助。

CancellationException可以使用 try-catch 捕获,但建议重新抛出它。

他还举了一个例子,

import kotlinx.coroutines.*

suspend fun main(): Unit = coroutineScope {
    val job = Job()
    launch(job) {
        try {
            repeat(1_000) { i ->
                delay(200)
                println("Printing $i")
            }
        } catch (e: CancellationException) {
            println(e)
            throw e
        }
    }
    delay(1100)
    job.cancelAndJoin()
    println("Cancelled successfully")
    delay(1000)
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它抓住catch (e: CancellationException)并重新抛出它,现在我想知道如果我不重新抛出它会发生什么。我注释掉了,throw e但代码照常执行。

正如我从kotlin doc中看到的,CancellationException 用于结构化并发,这意味着 CancellationException 不会取消父级,而是通知父级取消父级的所有其他子级(我在这里吗?)

我相信我的理解是错误的,下面的代码证明了这一点,

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        launch {
            println("Starting child 1")
            delay(1000)
            throw CancellationException("Close other child under this parent")
        }
        launch {
            println("Starting child 2")
            delay(5000)
            println("This should not be printed, but getting printed. Don't know why?")
        }
        delay(2000)
        println("Parent coroutine completed")
    }
    parentJob.join()
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我在其中创建了一个parentJob和两个子作业,然后我child 1通过抛出CancellationException并期望父作业必须取消child 2但没有取消来终止。

Sam*_*Sam 8

简短的回答:如果您必须捕获 a CancellationException,请ensureActive稍后调用以确保协程终止。

这是一个很大的问题,但不容易回答。有两点有助于理解解释。我认为这些要点解释了为什么您会在给出的代码示例中看到您不期望的行为。

  1. 抛出 aCancellationException 不会取消协程。
  2. 捕获CancellationException 并不会“取消取消”协程。

取消异常用作“快速退出”机制,以允许取消的协程停止它们正在执行的操作。然而,异常本身并不是决定协程是否被取消的因素。该信息作为协程的一部分单独存储Job

如果您有一个已取消的协程,该协程会生成取消异常,并且捕获该异常并且不重新抛出它,则可能会遇到问题,因为即使协程应该被取消,它也可能不会退出。

同样,如果您在尚未取消的协程中抛出取消异常,则可能会遇到这样的问题:即使抛出了异常,协程也会无提示地退出,而不会出现错误。

我在我的文章“导致协程崩溃的无声杀手”中详细介绍了这一点。

我的结论是,在捕获取消异常后,最安全的做法不是重新抛出它,而是调用ensureActive当前的协程上下文。