Job 是什么?
-
Job = 协程的生命周期句柄:表示一段协程工作是否开始、是否还在运行、是否已取消/完成。
-
作用:管理与观测(cancel / join / isActive / invokeOnCompletion)、形成父子层级(结构化并发)、承载取消原因。
取当前协程的 Job:val job = coroutineContext[Job]!!
生命周期状态机(简化)
New (LAZY) → Active → (Completing) → Completed(正常)
│ └→ Cancelling → Cancelled(带 cause)
└→ Cancelling → Cancelled
-
New:仅 CoroutineStart.LAZY 才会出现(还未启动)。
-
Active:已启动在跑;job.isActive == true。
-
Completing:主体已结束,等待子协程/回调收尾。
-
Cancelling/Cancelled:进入取消流程 / 最终取消。
-
Completed:正常完成。
父 Job 直到所有子 Job 完成才会进入最终 Completed/Cancelled(结构化并发保证“父等子”)。
父子传播规则(结构化并发)
- 子失败 ⇒ 取消父与兄弟(在 coroutineScope/普通 Job 层级里)。
- 父取消 ⇒ 取消所有子(同一个 CancellationException 原因向下传播)。
- 监督模型:用 supervisorScope 或 SupervisorJob,使“子失败不影响兄弟/父”。
// 普通父子:一死全死
coroutineScope {
val a = launch { … }
val b = launch { error("boom") } // 立刻取消 a 与父
}
// 监督:各玩各的
supervisorScope {
val a = launch { … }
val b = launch { error("boom") } // a 不受影响
}
取消与完成(异常模型)
-
取消 API:job.cancel(cause: CancellationException? = null),或 cancelAndJoin()(取消后挂起等待结束)。
-
取消本质:在下一个挂起点注入 CancellationException,触发你代码中的 try/finally 收尾。
-
获取原因:
- job.getCancellationException():取消必有 CancellationException。
- job.getCompletionExceptionOrNull():失败或取消的根因(正常完成为 null)。
-
完成回调:job.invokeOnCompletion { cause -> … }
-
cause == null 表示正常完成;否则是取消/失败的异常。
-
回调在线程语义:在哪个线程让它完成,就在哪个线程回调(不要在里头做重活)。
-
在 finally 里需要保证收尾不被取消:withContext(NonCancellable) { … }
Job vs Deferred(有无“结果”的区别)
-
Job:只有生命周期,没有返回值;join() 只等待,不抛出原始异常(最多 CancellationException)。
-
Deferred :Job 的子类型,带结果;await() 返回 T 或直接抛出原始异常。
-
经验:要结果用 async/await(Deferred),只做事用 launch/Job。
创建与组合
-
launch { … } → Job
-
async { … } → Deferred (也是 Job)
-
Job() / SupervisorJob() → 根 Job(CompletableJob) ,可作为 CoroutineScope(context + job) 的锚点。
-
joinAll(vararg jobs) / awaitAll(vararg deferreds):批量等待;awaitAll 任一失败会抛首个异常并取消其余。
Start 模式(与 Job 的关系)
-
DEFAULT:马上调度/运行。
-
LAZY:创建为 New,直到 start/join/await 或首次需要时才启动。
-
ATOMIC:启动到第一个挂起点前不可取消(避免“刚起就被取消”)。
-
UNDISPATCHED:当前线程一路执行到首个挂起点,再交给调度器(少一次切换)。
常用 API 速记
-
观测:isActive / isCompleted / isCancelled
-
等待:join()、cancelAndJoin()
-
取消:cancel(cause)、cancelChildren()
-
回调:invokeOnCompletion(onCancelling = false, invokeImmediately = false) { cause -> }
-
层级:children(遍历子协程)
-
诊断:getCompletionExceptionOrNull()、CoroutineName 标注日志
Android/后端实战范式
1) 防抖/换源:先取消旧 Job
private var searchJob: Job? = null
fun onQuery(q: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
val data = repo.search(q) // suspend
_state.value = data
}
}
2) 并发加载 + 一死全死(默认结构化)
suspend fun load(): Pair<User, Feed> = coroutineScope {
val u = async { api.user() }
val f = async { api.feed() }
u.await() to f.await() // 任一抛错,取消另一半与父
}
3) 并发容错(监督 + Result)
suspend fun loadTolerant(): Pair<Result<User>, Result<Feed>> = supervisorScope {
val u = async { runCatching { api.user() } }
val f = async { runCatching { api.feed() } }
u.await() to f.await()
}
4) 在 finally 确保收尾
launch(Dispatchers.IO) {
val handle = open()
try {
work()
} finally {
withContext(NonCancellable) { handle.close() }
}
}
5) 自定义根 Job 绑定组件生命周期
class MyComponent {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Main + job)
fun destroy() { job.cancel() } // 取消全部子协程
}
排错与坑
-
只 join() 不见异常:join() 不抛原始异常;用 Deferred.await() 或 getCompletionExceptionOrNull()。
-
忘了“父等子” :主体结束但子协程还在,父 Job 不会 Completed;确保子协程收尾或 cancelChildren()。
-
在锁内挂起:持锁时取消/超时会导致长时间占锁甚至死锁;把挂起点移出临界区或用无阻塞结构。
-
回调线程误用:invokeOnCompletion 回调运行在线程不确定处,避免操作 UI 或重工作业。
-
GlobalScope.async 不 await:异常“吞掉”;要么 await(),要么 invokeOnCompletion 做监控;更推荐结构化 scope。
-
取消不生效:CPU 密集循环里需协作取消:ensureActive() / yield() 或定期检查 isActive。
-
超时理解:withTimeout 抛 TimeoutCancellationException(CancellationException 子类),只取消子协程;如果没捕获,会按父子规则上抛。
一句话总结
- Job 管“生老病死”,Deferred 额外管“结果”。****
- 默认结构化并发:子失败会“上行取消父、横向取消兄弟”;监督模型可隔离失败。
- 学会三板斧:cancelAndJoin 收尾、invokeOnCompletion 监控、NonCancellable 保证清理。