CompletableDeferred是一个 可手动完成 的 Deferred, 它实现了 Deferred(可以 await()),也提供了 complete(value) / completeExceptionally(e) / cancel() 等方法,并由外部触发结果。它创建后可能处于未完成状态,任意线程或协程都能把它完成或失败,等待方使用 await() 来获取结果。
一、与Deferred、suspendCancellableCoroutine和Channel的区别
-
vs Deferred(由 async 返回):async 的 Deferred 是由协程体自己完成;CompletableDeferred 允许外部完成。
-
vs suspendCancellableCoroutine:两者都能把回调桥接到协程,suspendCancellableCoroutine 适合一次性封装回调;CompletableDeferred 更适合"外部多方在不同时间完成/通知"的场景(比如事件总线、跨协程信号、测试用的手动完成)。
-
vs Channel:Channel 是多次消息传递而CompletableDeferred 是单次结果(一次性)。
二、经典应用场景
1、桥接回调
一般的桥接回调推荐用suspendCancellableCoroutine,但如果外部多方的桥接推荐用CompletableDeferred。下面是一个简单的网络请求的例子。
kotlin
suspend fun Call.awaitResponse(): Response = suspendCancellableCoroutine { cont ->
enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (cont.isActive) cont.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
//response记得use{}关闭
if (cont.isActive) cont.resume(response)
}
})
cont.invokeOnCancellation { cancel() }
}
如果你想用 CompletableDeferred(当回调可能多次触发或在别处完成时非常好):
kotlin
fun makeRequest(): CompletableDeferred<String> {
val deferred = CompletableDeferred<String>()
httpClient.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
deferred.completeExceptionally(e)
}
override fun onResponse(call: Call, response: Response) {
val s = response.body?.string() ?: ""
deferred.complete(s)
response.close()
}
})
return deferred
}
// 使用处
lifecycleScope.launch {
try {
val result = makeRequest().await()
} catch (e: Throwable) { ... }
}
上面的桥接肯定推荐suspendCancellableCoroutine来完成,只是为了展示CompletableDeferred也可以完成回调的桥接。下面看一个多处回调桥接的例子:
kotlin
// 使用 CompletableDeferred 桥接多回调
suspend fun wakeUpDevice(): Boolean = coroutineScope {
val deferred = CompletableDeferred<Boolean>()
val finished = AtomicBoolean(false)
val failCount = AtomicInteger(0)
// 本地唤醒
sendLocalWakeUp(
onSuccess = {
if (finished.compareAndSet(false, true)) {
deferred.complete(true)
}
},
onFail = {
if (failCount.incrementAndGet() == 2) {
deferred.complete(false)
}
}
)
// 云端唤醒
sendCloudWakeUp(
onSuccess = {
if (finished.compareAndSet(false, true)) {
deferred.complete(true)
}
},
onFail = {
if (failCount.incrementAndGet() == 2) {
deferred.complete(false)
}
}
)
// 统一 suspend 等待结果
deferred.await()
}
// 示例调用
fun main() = runBlocking {
val result = wakeUpDevice()
println("唤醒结果:$result")
}
上面就是多处回调桥接的例子,只要有一次成功就成功,都失败才算失败。
2、多生产者单消费者"谁先到就用谁"
complete函数只能被调用一次,后续调用无效,基于这个特性可以完成多生产者单消费者-谁先到就用谁的需求。一个简单的示例如下:
kotlin
//两个异步来源(A、B)谁先返回就用谁的结果
val winner = CompletableDeferred<String>()
fun sourceA() { /* 异步获取 */ winner.complete("fromA") }
fun sourceB() { /* 异步获取 */ winner.complete("fromB") }
lifecycleScope.launch {
val result = winner.await() // 谁先 complete 就是结果
}
3、协程间通知
这是一个比较巧妙的用法,例如一个协程做准备工作,另一个协程等待完成:
kotlin
val ready = CompletableDeferred<Unit>()
launch {
prepareResources()
ready.complete(Unit)
}
launch {
ready.await() //挂起阻塞,等待准备(prepareResources)完成
startWork() //另一个协程的执行部分
}
扩展思维:ready.complete(Unit)还可以在用户输入后,点击按钮后等,可以发挥想象。
4、与select、超时结合
select 表达式同时等待多个挂起操作采用第一个完成的结果。
kotlin
fun CoroutineScope.fetchWithFallbacks(): CompletableDeferred<String> {
val result = CompletableDeferred<String>()
launch {
try {
val data = select<String> {
primarySource().onAwait { it }
secondarySource().onAwait { it }
onTimeout(800) { throw TimeoutException("Timeout") }
}
result.complete(data)
} catch (e: Exception) {
// 启动异步恢复任务(不立即completeExceptionally)
launch {
delay(1000)
try {
val recovery = recoverySource()
result.complete(recovery)
} catch (e2: Exception) {
result.completeExceptionally(e2) // 真正失败
}
}
}
}
return result
}
三、取消与异常传播
-
如果 CompletableDeferred 被 cancel(),等待的协程会收到 CancellationException。
-
如果在完成前等待者被取消,完成者仍然可以 complete() ------ 但 await() 不会再返回值(因为等待者已被取消)。
-
completeExceptionally(e) 会使 await() 抛出相同异常。
-
complete() 返回 true 或 false(表示是否第一次完成)。
四、注意事项与常见坑
-
不要泄漏未完成的 CompletableDeferred:把它长期暴露给全局可能会导致内存泄漏,尤其当负责完成它的组件被销毁时。
-
只在受信任的地方完成:不要让多个地方都可能完成且没序的代码混乱;若有竞态,记得检查 complete 的返回值。
-
避免把 CompletableDeferred 当作长生命周期队列:它是一次性完成的;若需要多次事件,请用 Channel / SharedFlow。
-
取消处理:如果等待者可能取消,使用 invokeOnCompletion 或 invokeOnCancellation 做清理(比如取消底层请求)。
-
完成后不要反复设置结果:complete 多次调用只有第一次有效,后续会返回 false。
-
与协程作用域的关系:CompletableDeferred 本身不是 Job,但它有 asJob() 可用于组合;也可以传入父 Job 构造(CompletableDeferred(parentJob))以便取消联动。