Kotlin 协程的 select 特性及其应用

select 和 Deferred 的结合

Deferred 是一种表示异步计算结果的对象,它可以通过 await 方法来获取结果,或者通过 isCompleted 属性来判断是否完成。如果有多个 Deferred 对象,我们可能想要获取最快的那个结果,或者优先显示缓存数据,再显示网络数据。这时,我们可以使用 select 来实现这样的逻辑。

select 是一个挂起函数,它接收一个 lambda 表达式作为参数,在 lambda 表达式中,我们可以使用 onAwait 方法来获取 Deferred 对象的结果,并返回一个值。select 会等待所有的 onAwait 回调,并选择最快的那个返回值作为结果。例如:
select+Deferred

java 复制代码
// 模拟从缓存中获取物品信息
suspend fun getCacheInfo(productId: String): Product {
    delay(100L)
    return Product(productId, 9.9, true)
}

// 模拟从网络中获取物品信息
suspend fun getNetworkInfo(productId: String): Product {
    delay(200L)
    return Product(productId, 9.8, false)
}

// 模拟更新UI
fun updateUI(product: Product) {
    println("${product.productId} == ${product.price} == ${product.isCache}")
}

// 数据类,来表示一个商品
data class Product(
    val productId: String,
    val price: Double,
    val isCache: Boolean = false
)

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val productId = "11211"
    // 开始两个非阻塞任务
    val cacheDeferred = async { getCacheInfo(productId) }
    val networkDeferred = async { getNetworkInfo(productId) }
    // 使用 select 来获取最快的结果
    val product = select<Product> {
        cacheDeferred.onAwait { it }
        networkDeferred.onAwait { it }
    }
    // 更新UI
    updateUI(product)
    println("total time : ${System.currentTimeMillis() - startTime}")
    // 如果当前是缓存信息,则再去获取网络信息
    if (product.isCache) {
        val latest = networkDeferred.await()
        updateUI(latest)
        println("all total time : ${System.currentTimeMillis() - startTime}")
    }
}

上面的代码中,我们模拟了从缓存和网络中获取商品信息的场景,由于缓存较快,我们使用 select 来优先显示缓存信息,然后再显示网络信息。运行结果如下:

java 复制代码
11211 == 9.9 == true
total time : 134
11211 == 9.8 == false
all total time : 235

可以看到,select 可以帮助我们实现优化缓存和网络数据的显示逻辑。

select 和 Channel 的结合

Channel 是一种用于协程间通信的数据结构,它可以发送和接收数据,并支持关闭和迭代。如果有多个 Channel ,我们可能想要获取最先发送数据的那个 Channel ,或者根据不同的 Channel 来执行不同的逻辑。这时,我们也可以使用 select 来实现这样的逻辑。

select 也可以接收 Channel 对象作为参数,在 lambda 表达式中,我们可以使用 onReceiveCatching 方法来获取 Channel 对象发送的数据,并返回一个值。select 会等待所有的 onReceiveCatching 回调,并选择最先发送数据的那个返回值作为结果。例如:


select +Channel

java 复制代码
fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    // 开启一个协程,往channel1中发送数据,这里发送完 ABC 需要 450ms
    val channel1 = produce {
        delay(50L)
        send("A")
        delay(150)
        send("B")
        delay(250)
        send("C")
        // 延迟 1000ms 是为了这个 Channel 不那么快 close
        // 因为 produce 高阶函数开启协程,当执行完时,会自动 close
        delay(1000)
    }
    // 开启一个协程,往channel2中发送数据,发送完 abc 需要 500ms
    val channel2 = produce {
        delay(100L)
        send("a")
        delay(200L)
        send("b")
        delay(200L)
        send("c")
        delay(1000)
    }
    // 选择 Channel ,接收两个 Channel
    suspend fun selectChannel(
        channel1: ReceiveChannel<String>,
        channel2: ReceiveChannel<String>
    ): String = select {
        // 这里同样使用类 onXXX 的 API
        channel1.onReceiveCatching { it.getOrNull() ?: "channel1 is closed!"¹[1] }
        channel2.onReceiveCatching { it.getOrNull() ?: "channel2 is closed!"¹[1] }
    }
    // 连续选择 6 次
    repeat(6) {
        val result = selectChannel(channel1, channel2)
        println(result)
    }
    // 最后再把协程取消,因为前面设置的有 1000ms 延迟
    channel1.cancel()
    channel2.cancel()
    println("Time cost: ${System.currentTimeMillis() - startTime}")
}

上面的代码中,我们模拟了两个 Channel 同时发送数据的场景,由于 Channel1 较快,我们使用 select 来优先接收 Channel1 的数据。运行结果如下:

java 复制代码
A
a
B
b
C
c
Time cost: 553

可以看到,select 可以帮助我们实现协程间的通信选择。

select 的使用注意事项和原理

select 的使用有一些注意事项和原理,我们需要了解一下:

  • select 是一个挂起函数,需要在协程中使用。
  • select 中的回调方法不是 await 或 receive ,而是 onAwait 或 onReceiveCatching 。
  • select 还不支持 Flow ,因为 Flow 已经有了类似的 API 。
  • select 的原理是基于协程的状态机和回调机制,它会注册所有的回调,并等待其中一个回调触发,然后取消其他的回调,并返回结果。

select 是一个非常有用的特性,它可以让我们在多个异步任务或通道中做出选择,从而实现优化或选择的逻辑。

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
weixin_4493108425 分钟前
高效集成:聚水潭采购数据同步到MySQL
android·数据库·mysql
Zender Han40 分钟前
Flutter自定义矩形进度条实现详解
android·flutter·ios
白乐天_n3 小时前
adb:Android调试桥
android·adb
一点媛艺7 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风7 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k10 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1010 小时前
JavaWeb项目-----博客系统
android
风和先行11 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.12 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰13 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder