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

协程子Job取消未触发父Job取消?Job与SupervisorJob差异解析

问题1:为何取消子Job后其他子Job未被取消?

根据Job文档说明:「使用非CancellationException调用cancel会同时取消父Job」,但Job.cancel仅接受CancellationException类型参数。测试代码如下:

fun main()  {
    val parentJob = Job()
    val scope = CoroutineScope(parentJob)

    suspend fun printText(text: String) {
        println("Before Delay: $text")
        delay(1000)
        println("After Delay: $text")
    }

    val job1 = scope.launch {
        printText("Job#1")
    }

    job1.invokeOnCompletion {
        println("Job#1 completed. Cause = $it")
    }

    val job2 = scope.launch {
        printText("Job#2")
    }

    job2.invokeOnCompletion {
        println("Job#2 completed. Cause = $it")
    }

    println(parentJob.children.joinToString { it.toString() } )

    job1.cancel(CancellationException())

    Thread.sleep(10000)
}

输出:

Before Delay: Job#1
Before Delay: Job#2
StandaloneCoroutine{Active}@462d5aee, StandaloneCoroutine{Active}@69b0fd6f
Job#1 completed. Cause = java.util.concurrent.CancellationException
After Delay: Job#2
Job#2 completed. Cause = null

Process finished with exit code 0

解答

文档描述的场景是子Job因未捕获的非CancellationException异常而失败,这种情况下才会触发父Job的取消,进而取消所有其他子Job。而你这里是主动调用job1.cancel(CancellationException()),属于协程的正常协作取消:CancellationException是协程取消的标准信号,这种取消不会向父Job传播,父Job和其他子Job会继续正常执行,所以job2不受影响。


问题2:Job与SupervisorJob的核心区别是什么?为何ViewModelScope要使用SupervisorJob?

扩展测试用例模拟Android ViewModel场景:创建CoroutineScope(Job())发起两个请求,其中一个抛出异常但被内部捕获,另一个任务仍正常执行。测试代码如下:

fun main() {
    val parentJob = Job()
    val scope = CoroutineScope(parentJob)

    suspend fun printText(text: String) {
        println("Before Delay: $text")
        if (text == "Job#1") {
            throw IllegalArgumentException("Test")
        }
        delay(1000)
        println("After Delay: $text")
    }

    val job1 = scope.launch {
        try {
            printText("Job#1")
        } catch (e: Exception) {
            println(e)
        }
    }

    job1.invokeOnCompletion {
        println("Job#1 completed. Cause = $it")
    }

    val job2 = scope.launch {
        printText("Job#2")
    }

    job2.invokeOnCompletion {
        println("Job#2 completed. Cause = $it")
    }

    println(parentJob.children.joinToString { it.toString() })

    Thread.sleep(10000)
}

输出:

Before Delay: Job#1
java.lang.IllegalArgumentException: Test
Job#1 completed. Cause = null
Before Delay: Job#2
StandaloneCoroutine{Active}@462d5aee
After Delay: Job#2
Job#2 completed. Cause = null

Process finished with exit code 0

核心区别

  • 普通Job:遵循失败传播规则——如果任何子Job因未捕获的非CancellationException异常失败,会立即取消父Job,父Job的取消会进一步传播给所有其他子Job,导致整个协程树被取消。
  • SupervisorJob:遵循失败隔离规则——子Job的失败(未捕获的非CancellationException异常)不会传播给父Job,其他子Job可以继续正常执行;但父Job的取消会正常传播给所有子Job。

ViewModelScope使用SupervisorJob的原因

ViewModel通常需要同时执行多个独立的任务(比如多个并行的网络请求、UI状态更新),这些任务彼此独立,某个任务的失败不应该影响其他任务的执行,也不应该导致ViewModel的整个协程作用域被取消。使用SupervisorJob可以实现子Job的失败隔离,保证单个任务的异常不会扩散到整个作用域,符合ViewModel的业务场景需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:40:31