在 Kotlin 协程中,Job.cancel() 和 Scope.cancel() 都用于取消协程,但它们的作用范围和行为有重要区别:
1. Job.cancel()
kotlin
val job = launch {
// 协程体
}
job.cancel() // 只取消这个特定的 job
特点:
- 只取消单个 Job 及其子协程
- 不会影响同一作用域中的其他 Job
- Job 被取消后,仍然可以附加新的子协程
kotlin
val job1 = scope.launch { /* 任务1 */ }
val job2 = scope.launch { /* 任务2 */ }
job1.cancel() // 只取消 job1,job2 继续运行
2. Scope.cancel()
kotlin
scope.cancel() // 取消整个作用域及其所有协程
特点:
- 取消整个作用域中的所有协程
- 作用域被取消后,不能再启动新的协程
- 所有子 Job 都会被取消
kotlin
val scope = CoroutineScope(Dispatchers.IO)
scope.launch { /* 任务1 */ }
scope.launch { /* 任务2 */ }
scope.cancel() // 取消所有任务,且 scope 不能再 launch 新协程
3. 关键区别对比
| 特性 | Job.cancel() | Scope.cancel() |
|---|---|---|
| 作用范围 | 单个 Job + 其子协程 | 整个作用域 + 所有协程 |
| 后续使用 | 可继续启动新协程 | 作用域已失效,不能启动新协程 |
| 状态影响 | Job 进入 Cancelling 状态 | Scope 和所有 Job 都取消 |
| 典型场景 | 取消特定任务 | 清理整个界面/组件资源 |
4. 实际例子
Job.cancel() 示例
kotlin
val scope = CoroutineScope(SupervisorJob())
val jobA = scope.launch {
delay(1000)
println("Job A 完成")
}
val jobB = scope.launch {
delay(2000)
println("Job B 完成")
}
// 只取消 jobA,jobB 继续运行
jobA.cancel()
// scope 仍然可用,可以启动新协程
scope.launch { println("新协程") }
Scope.cancel() 示例
kotlin
val scope = CoroutineScope(SupervisorJob())
scope.launch { /* 任务1 */ }
scope.launch { /* 任务2 */ }
// 取消整个作用域
scope.cancel()
// 这里会抛出异常:Scope is cancelled
scope.launch { println("这不会执行") }
5. 重要细节
Scope.cancel() 的内部实现
kotlin
kotlin
// CoroutineScope 的 cancel() 实际上调用了关联 Job 的 cancel()
public fun CoroutineScope.cancel(cause: CancellationException? = null) {
val job = coroutineContext[Job]
?: error("Scope cannot be cancelled because it does not have a job")
job.cancel(cause)
}
结构化并发的体现
- 在结构化并发中,通常使用
scope.cancel()来清理资源 - ViewModel 销毁时:
viewModelScope.cancel() - Activity 销毁时:
lifecycleScope.cancel()
异常处理差异
kotlin
// Job.cancel() 可以指定原因
job.cancel("用户取消操作")
// Scope.cancel() 同样可以
scope.cancel(TimeoutCancellationException("超时"))
6. 最佳实践建议
-
使用 Job.cancel() 当需要:
- 取消特定的后台任务
- 实现可取消的单个操作
- 不影响其他并行任务
-
使用 Scope.cancel() 当需要:
- 组件(Activity/Fragment)销毁时清理资源
- 取消整个工作流的所有任务
- 确保没有协程泄露
-
在 ViewModel 中:
kotlin
class MyViewModel : ViewModel() {
private val customScope = CoroutineScope(SupervisorJob())
fun startWork() {
customScope.launch { /* 工作 */ }
}
override fun onCleared() {
customScope.cancel() // 清理自定义作用域
super.onCleared()
}
// viewModelScope 会自动管理,无需手动取消
fun useViewModelScope() {
viewModelScope.launch { /* 自动绑定生命周期 */ }
}
}
核心总结 :Job.cancel() 是选择性取消 ,Scope.cancel() 是全面清理。在结构化并发中,通常让作用域管理生命周期,仅在特殊情况下单独取消特定 Job。