1) 它是什么:带“结果”的 Job
-
Deferred : Job —— Job + 一个将来可用的结果 T。
-
典型来源:async { /* 计算并 return T */ }。
-
读取结果的“正门”是 await() (挂起直到得到 T 或抛出异常)。
与 Job 的差异(最重要):
-
Job.join():只等结束,不给结果;失败通常表现为 CancellationException(CE)。
-
Deferred.await():成功返回 T;失败直接抛出原始异常(不是 CE 封装)。
常用成员(除 Job 的 API 外):
- suspend fun await(): T
- fun getCompleted(): T(非挂起,仅在已成功完成时可用,否则抛)
- fun getCompletionExceptionOrNull(): Throwable?(失败/取消的根因,成功为 null)
2) 结果与异常:装进盒子,await再打开
-
async 中抛出的异常会被封存到 Deferred;
- 在 await() 时抛出;
- 但如果处于 coroutineScope{} (默认结构化并发),子失败会立刻取消父作用域和兄弟任务,即便你还没 await。
-
在 supervisorScope{} / SupervisorJob 下,子失败不会影响兄弟/父;异常仍在 Deferred 里,只有 await() 才会抛。
口诀:要结果就 await();只等收尾用 join();想“互不影响”用监督(supervisorScope)+ runCatching。
3) 创建方式与组合
3.1async
suspend fun load(): Pair<User, Feed> = coroutineScope {
val u = async { api.user() } // Deferred<User>
val f = async { api.feed() } // Deferred<Feed>
u.await() to f.await()
}
3.2CompletableDeferred
- 手动完成的 Deferred,常用于回调桥接或对外暴露只读结果。
fun fetchAsDeferred(): Deferred<Response> {
val d = CompletableDeferred<Response>()
client.enqueue(object: Callback {
override fun onSuccess(r: Response) { d.complete(r) }
override fun onError(e: Throwable) { d.completeExceptionally(e) }
})
return d
}
3.3awaitAll / joinAll
val list: List<Deferred<Int>> = tasks.map { async { work(it) } }
val results: List<Int> = awaitAll(*list.toTypedArray()) // 任一失败→抛首错并取消其余
3.4select { deferred.onAwait { … } }
- 在多个 Deferred/Channel/超时间竞态时选先完成者(需要时再用)。
4) 取消与超时(务必熟悉)
-
取消 deferred.cancel();收尾用 cancelAndJoin() 。
-
被取消后 await() 会抛 CancellationException;根因可用 getCompletionExceptionOrNull() 取到。
-
withTimeout { async/await }:超时抛 TimeoutCancellationException(CE 子类),只取消其作用域内的子任务。
适配 CPU 密集型取消(必须协作):
while (isActive) { step() } // 或定期 check `ensureActive()` / `yield()`
5) Start 模式与LAZY的隐形坑
val a = async(start = LAZY) { slow() }
val b = async(start = LAZY) { slow2() }
// 直接 await 会“串行启动”:先触发 a,再触发 b
a.start(); b.start() // ✅ 想并发,记得先 start 再 await
val r = a.await() + b.await()
- DEFAULT:创建即调度/运行(可被优化为 inline 恢复)。
- LAZY:直到 start/await/join 才启动;多个 LAZY 想并发,务必先 start() 全部。
- UNDISPATCHED:在当前线程执行到首个挂起点再交给目标调度器(减少一次切换)。
6) 常见使用模式
6.1 并发汇总(失败即中断)
suspend fun load() = coroutineScope {
val a = async { partA() }
val b = async { partB() }
combine(a.await(), b.await())
}
6.2 容错并发(不互相拖垮)
suspend fun loadTolerant() = supervisorScope {
val a = async { runCatching { partA() } }
val b = async { runCatching { partB() } }
a.await() to b.await()
}
6.3 只监控不await(避免静默失败)
val d = async { job() }
d.invokeOnCompletion { e -> if (e != null) log("async failed", e) }
6.4 对外只暴露Deferred(读写分离)
private val _once = CompletableDeferred<Config>()
val config: Deferred<Config> get() = _once // 只读视图
7) Android/服务端落地建议
- UI 层:viewModelScope.async 做并发加载;在 onCleared() 由 SupervisorJob 取消所有子任务。
- 不要把 Deferred 保存到跨生命周期的静态/单例里;若确需缓存结果,用 StateFlow/SharedFlow 更合适。
- 监控异常:对关键 Deferred 加 invokeOnCompletion 上报;或统一 await() 外层 try/catch。
- 减少切换:Main.immediate / UNDISPATCHED 合理用在首段“轻量逻辑”,重活放 IO/Default。
8) 排错清单(踩坑必看)
- 只 join() 不 await() :拿不到原始异常 → 用 await() 或 getCompletionExceptionOrNull()。
- GlobalScope.async 不 await:异常静默;不建议这么用。
- 以为 async 失败不影响别人:在 coroutineScope 里会立刻取消父与兄弟;需要隔离请用 supervisorScope。
- LAZY 导致串行:先 start() 再 await()。
- 取消不生效:CPU 循环里要检查 isActive/ensureActive()。
- 在锁/临界区里 await:可能长时间持锁甚至死锁;把挂起点搬到锁外。
9) 速记
-
Deferred = Job + 结果;await() 拿结果 / 抛原始异常。
-
并发取结果:async + await / awaitAll;
-
互不影响:supervisorScope + runCatching;
-
外部完成:CompletableDeferred.complete/completeExceptionally;
-
LAZY 想并发:先 start() 再 await()。