Skip to content

Job 状态机深度解析

源:Job documentation

Job 的核心职责是管理协程的生命周期。它内部维护了一个复杂的状态机,确保父子关系和取消逻辑的正确执行。

六大状态与流转

一个 Job 包含以下六种逻辑状态,但在 API 层面,我们只能通过 isActiveisCompletedisCancelled 三个属性来观测。

状态isActiveisCompletedisCancelled描述
Newfalsefalsefalse已创建,但未启动(LAZY 模式)
Activetruefalsefalse运行中或等待子项
Completingtruefalsefalse自身完成,正在等待子项完成
Cancellingfalsefalsetrue正在被取消,等待清理或子项取消
Cancelledfalsetruetrue已被取消并清理完毕
Completedfalsetruefalse正常执行完毕

状态流转图

mermaid
graph TD
    New -- start --> Active
    Active -- finish --> Completing
    Active -- cancel --> Cancelling
    Completing -- all children finished --> Completed
    Cancelling -- all children finished --> Cancelled
    Active -- failure --> Cancelling

关键中间态:Completing

为什么需要 Completing 状态?

在结构化并发中,父协程的代码即便运行完了,它也必须等待所有子协程完成。此时父协程就处于 Completing。它依然是 isActive 的,但无法再启动新的子协程。

内部实现:状态字段

JobSupport 源码中,状态被存储在一个单一的 state 变量中(通常是 AtomicReference)。

  • Empty: 简单的初始态。
  • NodeList: 当有子项或监听器注册时,状态会升级为一个包含双向链表的对象。
  • Finishing: 对应 CompletingCancelling,内部持有一个列表来跟踪所有活跃的子项。

监听机制:invokeOnCompletion

你可以通过 invokeOnCompletion 在 Job 进入终态时执行回调。

kotlin
val job = launch { /* ... */ }
job.invokeOnCompletion { cause ->
    if (cause == null) {
        println("Job finished normally")
    } else {
        println("Job failed or cancelled with $cause")
    }
}

内存注意

invokeOnCompletion 返回一个 DisposableHandle。如果协程生命周期非常长,而你动态注册了大量监听器,记得手动 dispose,否则会造成内存压力。

核心准则总结

  1. 不要在 Cancelled 状态下尝试恢复业务:此时 Job 已不可用。
  2. 理解 isCompleted 的含义:它包含正常完成和取消。
  3. 善用 Completing 逻辑:这是确保父级能为子级“收尸”的底层保证。