You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程中是否需要重新抛出CancellationException?

Kotlin协程中CancellationException的两个疑问

我正在学习《Kotlin Coroutine Deep Dive》深入理解协程,书中提到:

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)
}

我测试注释掉throw e后代码仍正常执行,因此疑惑不重新抛出CancellationException会有什么影响。

另外,我根据Kotlin文档理解该异常用于结构化并发,不会取消父协程,而是通知父协程取消其所有其他子协程,但如下测试代码显示,抛出该异常后其他子协程并未被取消,想了解我的理解是否有误及其中原因:

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()
}

一、不重新抛出CancellationException的影响

  1. 协程状态异常:捕获CancellationException却不重新抛出时,协程的内部状态会被标记为「完成」而非「取消」。表面代码能正常执行,但协程的Job对象会认为自己是正常结束,而非被主动取消。
  2. 结构化并发连锁逻辑失效:如果该协程是父协程的子协程,父协程无法感知到它被取消的事实。比如在coroutineScope或supervisorScope中,父协程依赖子协程状态管理作用域生命周期,不抛出异常会导致父协程无法正确处理取消流程。
  3. 资源泄漏风险:协程被取消时,原本应通过finally、use等机制释放的资源,可能因协程状态异常无法正确触发。比如持有网络连接、文件句柄等资源时,不传递取消信号可能导致资源未及时释放。

二、关于抛出CancellationException后子协程未取消的理解误区

你的理解存在偏差,CancellationException的默认行为并非「通知父协程取消其他子协程」,具体逻辑如下:

  1. 普通CancellationException的作用:子协程抛出未捕获的CancellationException时,只会取消自身,不会影响父协程和其他子协程——除非这个异常是父协程发起的取消信号(比如调用job.cancel())。
  2. 结构化并发的异常传递规则:只有当子协程抛出非CancellationException的异常时,父协程才会被取消,进而连锁取消所有其他子协程。CancellationException是协作取消的信号,默认不会触发父协程的取消流程。
  3. 测试代码的问题分析:你的代码中,子协程1主动抛出CancellationException后,仅会取消自身;父协程和子协程2不受影响,因为这个异常不是父协程发起的取消指令。父协程会正常执行完delay(2000),子协程2的delay(5000)也会完成,因此会打印出预期外的内容。

如果要实现「子协程抛出异常后,父协程取消其他子协程」的效果,可以:

  • 抛出非CancellationException的自定义异常,触发父协程取消连锁;
  • 手动获取父协程的Job,调用parentJob.cancel()主动取消父协程及所有子协程。

示例修改代码:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        launch {
            println("Starting child 1")
            delay(1000)
            // 手动取消父协程,进而取消所有子协程
            coroutineContext.job.cancel()
        }
        launch {
            println("Starting child 2")
            delay(5000)
            println("This won't be printed now")
        }
        delay(2000)
        println("Parent coroutine won't complete either")
    }
    parentJob.join()
}

内容的提问来源于stack exchange,提问作者Vencat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 12:05:31