Job 是协程的句柄和生命周期管理器,cancel 只改状态不杀人 —— 协程的取消是协作式的
Job 是协程的句柄和生命周期管理器。它负责控制协程的启动、取消、完成等待、异常传播,并形成父子协程之间的结构化关系。
每个协程被创建时都会有一个与之关联的 Job 实例。它表示协程的运行状态:
isActive —— 协程是否仍在执行(未完成且未取消)isCompleted —— 协程是否已正常结束isCancelled —— 协程是否已被取消通过 Job 可以取消一个协程(job.cancel()),或者等待协程结束(job.join())。
val job = lifecycleScope.launch {
delay(5000)
println("任务完成")
}
// 通过 Job 句柄控制协程
job.cancel() // 取消协程
job.join() // 挂起等待协程结束
Job 接口继承自 CoroutineContext.Element,这意味着 Job 本身就是一个协程上下文的元素:
public interface Job : CoroutineContext.Element {
// Job 在协程上下文中的 Key
public companion object Key : CoroutineContext.Key<Job>
// ===== 状态属性(只读,由底层状态机维护) =====
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
// ===== 取消相关 =====
@InternalCoroutinesApi
public fun getCancellationException(): CancellationException
public fun cancel(cause: CancellationException? = null): Boolean
public fun cancel(message: String, cause: Throwable? = null): Boolean
// ===== 启动与等待 =====
public fun start(): Boolean
public suspend fun join()
public val onJoin: SelectClause0
// ===== 子协程管理 =====
public val children: Sequence<Job>
@InternalCoroutinesApi
public fun attachChild(child: ChildJob): ChildHandle
// ===== 完成回调 =====
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
@InternalCoroutinesApi
public fun invokeOnCompletion(
onCancelling: Boolean = false,
invokeImmediately: Boolean = true,
handler: CompletionHandler
): DisposableHandle
// ===== 已废弃:两个 Job 不能相加 =====
@Deprecated(
message = "Operator '+' on two Job objects is meaningless. " +
"Job is a coroutine context element and `+` is a set-sum operator. " +
"The job to the right of `+` just replaces the job the left of `+`.",
level = DeprecationLevel.ERROR
)
public operator fun plus(other: Job): Job = other
}
isActive、isCompleted、isCancelled 这三个属性都是只读的,由底层的状态机维护。你无法手动修改它们,只能通过 cancel() 等方法间接触发状态迁移。
注意 plus 操作符已被标记为 DeprecationLevel.ERROR。两个 Job 对象的 + 运算没有意义 —— Job 是协程上下文元素,+ 是上下文的集合求和运算,右边的 Job 只会替换掉左边的 Job,不会合并。
回顾 launch 的源码,它声明返回 Job,但实际上创建并返回的是 StandaloneCoroutine 或 LazyStandaloneCoroutine:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine // 返回 StandaloneCoroutine,类型为 Job
}
StandaloneCoroutine 的源码非常简洁:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
private class LazyStandaloneCoroutine(
parentContext: CoroutineContext,
block: suspend CoroutineScope.() -> Unit
) : StandaloneCoroutine(parentContext, active = false) {
private val continuation = block.createCoroutineUnintercepted(this, this)
override fun onStart() {
continuation.startCoroutineCancellable(this)
}
}
真正的重量级实现在 AbstractCoroutine:
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
// 继承了 JobSupport(状态机实现)
// 实现了 Job 接口
// 实现了 Continuation(续体 —— 协程底层)
// 实现了 CoroutineScope(所以协程体内可以启动子协程)
}
核心在 JobSupport。它是一个非常长的类(状态机实现),源码中包含一个注释形式的文字版流程图,清晰展示了 Job 状态的流转路径。
JobSupport 中的状态机定义了以下状态:
关键点:
cancel() 触发,进入取消中(等待子协程完成取消)核心认知:cancel() 本身只是将 Job 的状态迁移到"取消中/已取消",不会强行终止协程。协程是否真正停止,取决于它是否在适当的时机(通常是挂起恢复点)检查这个状态并抛出 CancellationException。
标准库的挂起函数(delay、join、await、yield 等)在恢复执行前都会调用 ensureActive() 或类似检查。如果此时协程已被取消,它们就会抛出 CancellationException,从而终止协程。
来看一个无法取消的例子:
// cancel() 无效!协程永远不会停止
val job = lifecycleScope.launch {
var i = 0
while (true) {
i++ // 纯计算,没有任何挂起点
}
}
job.cancel() // 改了状态,但协程永远不检查
要让上面的协程能被取消,必须手动检查取消状态:
// 方式一:检查 isActive
val job = lifecycleScope.launch {
var i = 0
while (isActive) { // 每次循环检查
i++
}
}
job.cancel() // 下次循环时 isActive 变为 false,循环退出
// 方式二:使用 yield()
val job = lifecycleScope.launch {
var i = 0
while (true) {
yield() // 恢复时自动检查取消,如已取消则抛出异常
i++
}
}
job.cancel() // yield() 检测到取消,抛出 CancellationException
isActive 手动检查yield() 自动检查ensureActive() 显式检查协程的取消是协作式的 —— cancel() 只是发出"请取消"的信号,协程需要主动配合检查才能停下来。这和 Thread.stop() 那种强制终止完全不同(Thread.stop() 已被标记为废弃,正是因为它不安全)。
Job 是协程的灵魂 —— 它管理生命周期、维护状态机、组织父子关系,而 cancel() 只是一个协作信号,协程的生死最终取决于它自己是否主动检查。