未指定dispatcher启动的Kotlin协程调用cancel后无法取消的原因是什么
问题核心原因
两个示例的行为差异本质是协程调度器的线程模型不同,导致取消操作的执行时机不同。
可正常取消的示例逻辑(指定Dispatchers.Default)
launch(Dispatchers.Default)会将子协程调度到Kotlin默认的后台共享线程池运行,和runBlocking所在的主线程是完全独立的执行线程。- 父协程启动子协程后,会立刻继续执行后续的
job.cancelAndJoin()代码,将子协程的状态标记为已取消。 - 子协程的循环每次判断
isActive属性时,都能读取到最新的取消状态,因此循环会退出,协程正常结束。
无法正常取消的示例逻辑(未指定调度器)
- 调用
launch不传调度器时,子协程会继承父协程runBlocking的默认调度器:这是一个绑定到当前调用线程(主线程)的单线程调度器,所有属于该调度器的协程任务都需要排队在这同一个线程上执行。 runBlocking的调度器有一个特殊执行逻辑:启动新的子协程时,会优先执行子协程的代码,直到子协程遇到第一个挂起点,才会将执行权交还给父协程。- 你的子协程中的
while循环是纯计算逻辑,没有调用任何挂起函数,不存在任何挂起点,因此子协程一旦开始运行,就会一直占用主线程无限执行循环,父协程的job.cancelAndJoin()代码永远得不到执行机会,自然不会触发取消操作,看起来就像协程无法取消。
验证方式
你可以在第二个示例的while循环中加入挂起函数yield(),主动让出线程执行权,就能看到协程可以正常取消:
fun main() = runBlocking { val job = launch { println("Start job 2") var i =0 while (isActive){ print(i++) print(i--) yield() // 加入挂起点,让出执行权 } println("End job 2") } job.cancelAndJoin() }
内容的提问来源于stack exchange,提问作者Jitendra A
相关产品推荐
相关产品推荐

