kotlin协程 容易被忽视的CompletableDeferred

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(表示是否第一次完成)。

四、注意事项与常见坑

  1. 不要泄漏未完成的 CompletableDeferred:把它长期暴露给全局可能会导致内存泄漏,尤其当负责完成它的组件被销毁时。

  2. 只在受信任的地方完成:不要让多个地方都可能完成且没序的代码混乱;若有竞态,记得检查 complete 的返回值。

  3. 避免把 CompletableDeferred 当作长生命周期队列:它是一次性完成的;若需要多次事件,请用 Channel / SharedFlow。

  4. 取消处理:如果等待者可能取消,使用 invokeOnCompletion 或 invokeOnCancellation 做清理(比如取消底层请求)。

  5. 完成后不要反复设置结果:complete 多次调用只有第一次有效,后续会返回 false。

  6. 与协程作用域的关系:CompletableDeferred 本身不是 Job,但它有 asJob() 可用于组合;也可以传入父 Job 构造(CompletableDeferred(parentJob))以便取消联动。

相关推荐
雨白7 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk8 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING8 小时前
RN容器启动优化实践
android·react native
恋猫de小郭11 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker16 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴16 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos