Job接口与协程状态管理

Job 是协程的句柄和生命周期管理器,cancel 只改状态不杀人 —— 协程的取消是协作式的

Job JobSupport cancel join 状态机 AbstractCoroutine StandaloneCoroutine isActive

目录导航(点击跳转)

一、Job:协程的句柄与生命周期管理器

1Job 的职责

Job 是协程的句柄和生命周期管理器。它负责控制协程的启动、取消、完成等待、异常传播,并形成父子协程之间的结构化关系。

每个协程被创建时都会有一个与之关联的 Job 实例。它表示协程的运行状态:

  • isActive —— 协程是否仍在执行(未完成且未取消)
  • isCompleted —— 协程是否已正常结束
  • isCancelled —— 协程是否已被取消

通过 Job 可以取消一个协程job.cancel()),或者等待协程结束job.join())。

val job = lifecycleScope.launch {
    delay(5000)
    println("任务完成")
}

// 通过 Job 句柄控制协程
job.cancel()   // 取消协程
job.join()     // 挂起等待协程结束
launch { }
返回
Job
提供
cancel()
取消
join()
等待完成
isActive / isCompleted
/ isCancelled
状态查询
children
子协程管理

二、Job 接口源码分析

2Job 是一个 CoroutineContext.Element

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
}

isActiveisCompletedisCancelled 这三个属性都是只读的,由底层的状态机维护。你无法手动修改它们,只能通过 cancel() 等方法间接触发状态迁移。

注意 plus 操作符已被标记为 DeprecationLevel.ERROR。两个 Job 对象的 + 运算没有意义 —— Job 是协程上下文元素,+ 是上下文的集合求和运算,右边的 Job 只会替换掉左边的 Job,不会合并。

三、从 Job 到 StandaloneCoroutine:继承链

3launch 返回的实际上是 Job 的子类

回顾 launch 的源码,它声明返回 Job,但实际上创建并返回的是 StandaloneCoroutineLazyStandaloneCoroutine

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(所以协程体内可以启动子协程)
}
接口层
|
Job Continuation<T> CoroutineScope
|
↑ 实现 ↑
|
JobSupport (状态机核心)
|
↑ 继承 ↑
|
AbstractCoroutine<T>
|
↑ 继承 ↑
|
StandaloneCoroutine LazyStandaloneCoroutine

核心在 JobSupport。它是一个非常长的类(状态机实现),源码中包含一个注释形式的文字版流程图,清晰展示了 Job 状态的流转路径。

4Job 状态机流转

JobSupport 中的状态机定义了以下状态:

1
New
初始创建
2
Active
正在执行
3
Cancelling
取消中
4
Cancelled
已取消
2
Active
3'
Completing
完成中
4'
Completed
已完成

关键点:

  • New → Active:协程启动后自动进入 Active 状态
  • Active → Cancelling:调用 cancel() 触发,进入取消中(等待子协程完成取消)
  • Cancelling → Cancelled:所有子协程取消完毕,进入已取消终态
  • Active → Completing:协程体执行完毕,等待子协程完成
  • Completing → Completed:所有子协程完成,进入已完成终态

四、取消是协作式的:cancel() 不会强行终止

5cancel() 只改状态,不强制杀协程

核心认知:cancel() 本身只是将 Job 的状态迁移到"取消中/已取消",不会强行终止协程。协程是否真正停止,取决于它是否在适当的时机(通常是挂起恢复点)检查这个状态并抛出 CancellationException

标准库的挂起函数(delayjoinawaityield 等)在恢复执行前都会调用 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

纯计算循环(无法取消)

  • 没有挂起点
  • 永远不检查取消状态
  • cancel() 改状态但协程不停
  • 线程被永久占用

加入取消检查(可取消)

  • isActive 手动检查
  • yield() 自动检查
  • ensureActive() 显式检查
  • 协程能响应 cancel()

协程的取消是协作式的 —— cancel() 只是发出"请取消"的信号,协程需要主动配合检查才能停下来。这和 Thread.stop() 那种强制终止完全不同(Thread.stop() 已被标记为废弃,正是因为它不安全)。

五、核心思想总结

核心要点

一句话总结

Job 是协程的灵魂 —— 它管理生命周期、维护状态机、组织父子关系,而 cancel() 只是一个协作信号,协程的生死最终取决于它自己是否主动检查。