在 Kotlin 协程中,CoroutineScope.cancel() 和 Job.cancel() 都是用于取消协程的核心操作,二者紧密关联但作用范围和场景不同。理解它们的联系与区别,需要从协程的结构化并发模型入手。
一、核心联系:基于 Job 的层级关系
CoroutineScope(协程作用域)和 Job(协程任务)的本质是**“作用域通过 Job 管理协程生命周期”**,二者的取消操作存在直接关联:
-
Scope 依赖 Job 实现取消
每个CoroutineScope都包含一个coroutineContext,而coroutineContext中必须包含一个Job实例(作为协程的“根任务”)。当调用scope.cancel()时,本质是调用了该scope.coroutineContext[Job]?.cancel()——即取消作用域的根 Job。 -
Job 构成层级树
协程启动时会创建一个Job,且该 Job 会成为其父 Job(通常是 Scope 的根 Job)的子 Job,形成Job 层级树。因此:- 取消父 Job 会递归取消其所有子 Job(子协程);
- 取消 Scope 的根 Job(即
scope.cancel())会取消该 Scope 下所有协程(因为它们都是根 Job 的子 Job)。
二、核心区别:作用范围与使用场景
| 维度 | CoroutineScope.cancel() | Job.cancel() |
|---|---|---|
| 作用对象 | 取消整个作用域内的所有协程(通过取消其根 Job) | 取消单个 Job 及其所有子 Job(单个协程或一组子协程) |
| 对作用域的影响 | 取消后,该 Scope 无法再启动新协程(根 Job 已失效) | 仅影响当前 Job 及其子 Job,不影响其所在 Scope 的其他 Job |
| 典型使用场景 | 销毁组件时清理所有协程(如 Activity 销毁时取消 lifecycleScope) | 取消单个耗时任务(如用户主动终止某个请求) |
1. 作用范围:整体 vs 局部
-
CoroutineScope.cancel():作用于整个作用域。
例如,lifecycleScope.cancel()会取消 Activity/Fragment 中通过lifecycleScope启动的所有协程,因为它们都依附于lifecycleScope的根 Job。 -
Job.cancel():作用于单个 Job 及其子 Job。
例如,启动协程时保存其 Job,后续可单独取消:val scope = CoroutineScope(Job()) // 启动协程并保存 Job val job = scope.launch { delay(1000) println("任务1") } // 启动另一个协程 scope.launch { delay(2000) println("任务2") } job.cancel() // 仅取消“任务1”,“任务2”不受影响
2. 对作用域可用性的影响
-
CoroutineScope.cancel()会导致作用域失效:
当 Scope 的根 Job 被取消后,该 Scope 无法再启动新协程(新协程会立即被取消)。val scope = CoroutineScope(Job()) scope.cancel() // 取消根 Job scope.launch { println("不会执行") // 启动即取消 } -
Job.cancel()不影响作用域本身:
即使取消了某个子 Job,其所在的 Scope 仍可正常启动新协程(只要 Scope 的根 Job 未被取消)。val scope = CoroutineScope(Job()) val job = scope.launch { delay(1000) } job.cancel() // 取消子 Job scope.launch { println("会执行") // Scope 仍有效 }
3. 与结构化并发的关联
CoroutineScope 是结构化并发的核心载体,其设计目的是“确保作用域内的所有协程都能被正确管理(启动、取消、异常处理)”。因此:
scope.cancel()是“销毁整个作用域”的标准操作,通常与组件生命周期绑定(如ViewModel.onCleared()中取消viewModelScope)。Job.cancel()是“局部取消”的操作,用于精细化控制单个任务,不破坏作用域的整体结构。
三、总结
- 联系:
scope.cancel()本质是取消 Scope 根 Job,二者都通过 Job 的层级关系实现协程取消,且取消操作会递归影响子 Job。 - 区别:
scope.cancel()是“整体取消”(作用域内所有协程,且使作用域失效),Job.cancel()是“局部取消”(单个 Job 及其子 Job,不影响作用域)。
实际开发中,应优先使用 scope.cancel() 管理组件生命周期内的所有协程(避免泄漏),仅在需要单独控制某个任务时使用 Job.cancel()。
4576

被折叠的 条评论
为什么被折叠?



