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

为何将父协程上下文Job设为Job()未触发子协程取消?

问题描述

在以下代码中,使用launch在scope1中构建协程并传入Job()实例,预期任一子协程取消会导致父协程取消;同时使用awaitAll,根据官方文档,该函数会在任一deferred失败时立即抛出异常。

val scope1 = CoroutineScope(Dispatchers.Default)
val result = IntArray(4)

fun main() = runBlocking {
    val parentJob = scope1.launch(Job()) {
        val childJobList = (1..4).map { i ->
            async {
                delay(i.toLong() * 1000.toLong())
                if (i == 2) {
                    cancel()
                }
                yield()
                println("async $i is done")
                result[i - 1] = i
            }
        }
        try {
            childJobList.awaitAll()
            println("Child job completed successfully")
        } catch (e: Exception) {
            println("Caught exception in parent: ${e.message}")
        }
    }

    parentJob.join()
    println("Parent job completed; result = ${result.contentToString()}")
}

但实际运行结果与预期不符,输出如下:

async 1 is done
Caught exception in parent: DeferredCoroutine was cancelled
async 3 is done
async 4 is done
Parent job completed; result = [1, 0, 3, 4]
原因分析
  • 异常被捕获,父协程未触发取消:awaitAll确实会在第一个失败的Deferred(i=2的async协程被取消)抛出CancellationException,但该异常被父协程中的try-catch块捕获处理了。异常没有向上传播,父协程的Job不会因此进入取消状态,仍保持活跃。
  • 未取消的子协程不受影响:i=3、4的async协程的父Job是父协程的Job,由于父协程未被取消,这两个子协程会继续执行直到完成,所以会输出对应的日志并写入结果数组。
  • 主动cancel子协程不会触发父协程取消:你预期“任一子协程取消会导致父协程取消”,但只有当子协程抛出未捕获的异常时,默认的Job(非SupervisorJob)才会将失败传播给父协程。这里i=2的协程是主动调用cancel(),属于正常取消操作,不会触发父协程的取消逻辑;即便awaitAll将其转化为异常抛出,也被你捕获处理了,父协程仍能继续运行。
  • 父协程Job未被终止,正常执行到结束:你给launch传入的Job()实例作为父协程的Job,只要父协程没有未处理的异常、也没有被主动取消,就会运行到代码块结束。父协程在catch块执行完成后正常结束,最终join()等待完成后输出对应的结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:25:08