为何将父协程上下文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
相关产品推荐
相关产品推荐

