Kotlin 协程的 select 特性及其应用
  mkIZDEUN7jdf 2023年11月02日 34 0

select 和 Deferred 的结合

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

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

Kotlin 协程的 select 特性及其应用_缓存

// 模拟从缓存中获取物品信息
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 来优先显示缓存信息,然后再显示网络信息。运行结果如下:

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 回调,并选择最先发送数据的那个返回值作为结果。例如:

Kotlin 协程的 select 特性及其应用_缓存_02

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 的数据。运行结果如下:

A
a
B
b
C
c
Time cost: 553

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

select 的使用注意事项和原理

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

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

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

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

mkIZDEUN7jdf