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老皮!!!欢迎大家来找我探讨交流👀