为何中间协程会阻塞顶级协程?SupervisorJob使用问题排查
协程父Job选择导致的执行挂起问题
需求与问题复现
需要实现串行-并行处理大列表:将大列表分块,依次处理每个分块,每个分块内的元素并行处理。简化后的问题复现代码如下:
val rootScope = CoroutineScope(Dispatchers.IO) val list: List<Int> = listOf(1,2,3,4,5) rootScope.launch { val rootLocalScope = this log("-----> rootScope (start)") launch { val mediumLocalScope = this list.map { i -> // 将rootLocalScope替换为mediumLocalScope后程序挂起 launch (SupervisorJob(rootLocalScope.coroutineContext.job)) { log("Downloading file-$i") delay(1000) } }.joinAll() }.join() log("-----> rootScope (finish)") }
备注
所有.join()都是必需的,缺少则无法保证正确的执行顺序。
执行现象
- 使用
SupervisorJob(rootLocalScope.coroutineContext.job)时,程序输出正常,顶级协程能执行到finish; - 使用
SupervisorJob(mediumLocalScope.coroutineContext.job)时,顶级协程无法执行到finish,程序出现挂起。
核心问题
为什么用mediumLocalScope的Job作为SupervisorJob的父级时,程序会挂起?
问题原因
核心问题出在手动创建SupervisorJob时的父子Job生命周期冲突:
- 当你把
mediumLocalScope.coroutineContext.job设为SupervisorJob的父级时,这个SupervisorJob会成为mediumLocalScope协程Job的直接子Job。 mediumLocalScope协程的执行逻辑是:创建所有子协程后调用joinAll()等待它们完成,随后协程结束。但mediumLocalScope的Job在协程结束前,会自动等待所有子Job(也就是你手动创建的SupervisorJob)完成。- 而
SupervisorJob本身也会等待自己的所有子Job(即那些处理文件下载的协程)完成。此时就形成了一个隐含的等待链:mediumLocalScope.join()→ 等待mediumLocalScope协程结束 → 等待joinAll()完成 → 等待下载协程完成;同时mediumLocalScope的Job → 等待SupervisorJob完成 → 等待下载协程完成。 - 当所有下载协程完成后,
joinAll()执行完毕,mediumLocalScope协程准备结束,但由于手动创建的SupervisorJob没有正确绑定到mediumLocalScope的协程作用域,mediumLocalScope的Job无法及时感知到SupervisorJob已经完成,最终导致mediumLocalScope.join()永远无法结束,顶级协程也就卡在这一步,无法执行到finish。
正确实现方案
不需要手动创建SupervisorJob,直接使用supervisorScope来实现分块内的并行处理,同时保证分块的串行执行:
val rootScope = CoroutineScope(Dispatchers.IO) val list: List<Int> = listOf(1,2,3,4,5) rootScope.launch { log("-----> rootScope (start)") // 按需求分块,示例分块大小为2 list.chunked(2).forEach { chunk -> // supervisorScope自动管理子协程生命周期:等待所有子协程完成,且单个子协程失败不影响其他 supervisorScope { chunk.forEach { i -> launch { log("Downloading file-$i") delay(1000) } } } } log("-----> rootScope (finish)") }
这种方式下:
- 外层
forEach保证分块的串行处理; - 每个
supervisorScope内部的launch实现分块内元素的并行处理; supervisorScope会自动等待所有子协程完成,无需手动调用joinAll(),同时避免了手动管理Job带来的生命周期冲突。
内容的提问来源于stack exchange,提问作者An12
相关产品推荐
相关产品推荐

