Kotlin?select使用方法介紹
一、select是什么
select——>用于選擇更快的結(jié)果。
基于場(chǎng)景理解
比如客戶端要查詢一個(gè)商品的詳情。兩個(gè)服務(wù):緩存服務(wù),速度快但信息可能是舊的;網(wǎng)絡(luò)服務(wù),速度慢但信息一定是最新的。
如何實(shí)現(xiàn)上述邏輯:
runBlocking {
suspend fun getCacheInfo(productId: String): Product {
delay(100L)
return Product(productId, 8.9)
}
suspend fun getNetworkInfo(productId: String): Product? {
delay(200L)
return Product(productId, 8.8)
}
fun updateUI(product: Product) {
println("${product.productId} : ${product.price}")
}
val startTime = System.currentTimeMillis()
val productId = "001"
val cacheInfo = getCacheInfo(productId)
if (cacheInfo != null) {
updateUI(cacheInfo)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
val latestInfo = getNetworkInfo(productId)
if (latestInfo != null) {
updateUI(latestInfo)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
}001 : 8.9
Time cost: 113
001 : 8.8
Time cost: 324
上述程序分為四步:第一步:查詢緩存信息;第二步:緩存服務(wù)返回信息,更新 UI;第三步:查詢網(wǎng)絡(luò)服務(wù);第四步:網(wǎng)絡(luò)服務(wù)返回信息,更新 UI。
用戶可以第一時(shí)間看到商品的信息,雖然它暫時(shí)會(huì)展示舊的信息,但由于我們同時(shí)查詢了網(wǎng)絡(luò)服務(wù),舊緩存信息也馬上會(huì)被替代成新的信息。但是可能存在一些問題:如果程序卡在了緩存服務(wù),獲取網(wǎng)絡(luò)服務(wù)就會(huì)無法執(zhí)行。是因?yàn)?getCacheInfo() 它是一個(gè)掛起函數(shù),只有這個(gè)程序執(zhí)行成功以后,才可以繼續(xù)執(zhí)行后面的任務(wù)。能否做到:兩個(gè)掛起函數(shù)同時(shí)執(zhí)行,誰返回的速度更快,就選擇哪個(gè)結(jié)果。答案是使用select。
runBlocking {
suspend fun getCacheInfo(productId: String): Product {
delay(100L)
return Product(productId, 8.9)
}
suspend fun getNetworkInfo(productId: String): Product {
delay(200L)
return Product(productId, 8.8)
}
fun updateUI(product: Product) {
println("${product.productId} : ${product.price}")
}
val startTime = System.currentTimeMillis()
val productId = "001"
val product = select<Product?> {
async {
getCacheInfo(productId)
}.onAwait {
it
}
async {
getNetworkInfo(productId)
}.onAwait {
it
}
}
if (product != null) {
updateUI(product)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
}001 : 8.9
Time cost: 134
Process finished with exit code 0
由于緩存的服務(wù)更快,所以,select 確實(shí)幫我們選擇了更快的那個(gè)結(jié)果。我們的 select 可以在緩存服務(wù)出現(xiàn)問題的時(shí)候,靈活選擇網(wǎng)絡(luò)服務(wù)的結(jié)果。從而避免用戶等待太長(zhǎng)的時(shí)間,得到糟糕的體驗(yàn)。
在上述代碼中,用戶大概率是會(huì)展示舊的緩存信息。但實(shí)際場(chǎng)景下,我們是需要進(jìn)一步更新最新信息的。
runBlocking {
suspend fun getCacheInfo(productId: String): Product {
delay(100L)
return Product(productId, 8.9)
}
suspend fun getNetworkInfo(productId: String): Product {
delay(200L)
return Product(productId, 8.8)
}
fun updateUI(product: Product) {
println("${product.productId} : ${product.price}")
}
val startTime = System.currentTimeMillis()
val productId = "001"
val cacheDeferred = async {
getCacheInfo(productId)
}
val latestDeferred = async {
getNetworkInfo(productId)
}
val product = select<Product?> {
cacheDeferred.onAwait {
it.copy(isCache = true)
}
latestDeferred.onAwait {
it.copy(isCache = false)
}
}
if (product != null) {
updateUI(product)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
if (product != null && product.isCache) {
val latest = latestDeferred.await() ?: return@runBlocking
updateUI(latest)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
}001 : 8.9
Time cost: 124
001 : 8.8
Time cost: 228
Process finished with exit code 0
如果是多個(gè)服務(wù)的緩存場(chǎng)景呢?
runBlocking {
val startTime = System.currentTimeMillis()
val productId = "001"
suspend fun getCacheInfo(productId: String): Product {
delay(100L)
return Product(productId, 8.9)
}
suspend fun getCacheInfo2(productId: String): Product {
delay(50L)
return Product(productId, 8.85)
}
suspend fun getNetworkInfo(productId: String): Product {
delay(200L)
return Product(productId, 8.8)
}
fun updateUI(product: Product) {
println("${product.productId} : ${product.price}")
}
val cacheDeferred = async {
getCacheInfo(productId)
}
val cacheDeferred2 = async {
getCacheInfo2(productId)
}
val latestDeferred = async {
getNetworkInfo(productId)
}
val product = select<Product?> {
cacheDeferred.onAwait {
it.copy(isCache = true)
}
cacheDeferred2.onAwait {
it.copy(isCache = true)
}
latestDeferred.onAwait {
it.copy(isCache = true)
}
}
if (product != null) {
updateUI(product)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
if (product != null && product.isCache) {
val latest = latestDeferred.await()
updateUI(latest)
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
}Log
001 : 8.85
Time cost: 79
001 : 8.8
Time cost: 229
Process finished with exit code 0
select 代碼模式,可以提升程序的整體響應(yīng)速度。
二、select和Channel
runBlocking {
val startTime = System.currentTimeMillis()
val channel1 = produce {
send(1)
delay(200L)
send(2)
delay(200L)
send(3)
}
val channel2 = produce {
delay(100L)
send("a")
delay(200L)
send("b")
delay(200L)
send("c")
}
channel1.consumeEach {
println(it)
}
channel2.consumeEach {
println(it)
}
println("Time cost: ${System.currentTimeMillis() - startTime}")
}Log
1
2
3
a
b
c
Time cost: 853
Process finished with exit code 0
上述代碼串行執(zhí)行,可以使用select進(jìn)行優(yōu)化。
runBlocking {
val startTime = System.currentTimeMillis()
val channel1 = produce {
send(1)
delay(200L)
send(2)
delay(200L)
send(3)
}
val channel2 = produce {
delay(100L)
send("a")
delay(200L)
send("b")
delay(200L)
send("c")
}
suspend fun selectChannel(
channel1: ReceiveChannel<Int>,
channel2: ReceiveChannel<String>
): Any {
return select<Any> {
if (!channel1.isClosedForReceive) {
channel1.onReceive {
it.also {
println(it)
}
}
}
if (!channel2.isClosedForReceive) {
channel2.onReceive {
it.also {
println(it)
}
}
}
}
}
repeat(6) {
selectChannel(channel1, channel2)
}
println("Time cost: ${System.currentTimeMillis() - startTime}")
}Log
1
a
2
b
3
c
Time cost: 574
Process finished with exit code 0
從代碼執(zhí)行結(jié)果可以發(fā)現(xiàn)程序的執(zhí)行耗時(shí)有效減少。onReceive{} 是 Channel 在 select 當(dāng)中的語(yǔ)法,當(dāng) Channel 當(dāng)中有數(shù)據(jù)以后,它就會(huì)被回調(diào),通過這個(gè) Lambda,將結(jié)果傳出去。執(zhí)行了 6 次 select,目的是要把兩個(gè)管道中的所有數(shù)據(jù)都消耗掉。
如果Channel1不生產(chǎn)數(shù)據(jù)了,程序會(huì)如何執(zhí)行?
runBlocking {
val startTime = System.currentTimeMillis()
val channel1 = produce<String> {
delay(5000L)
}
val channel2 = produce<String> {
delay(100L)
send("a")
delay(200L)
send("b")
delay(200L)
send("c")
}
suspend fun selectChannel(
channel1: ReceiveChannel<String>,
channel2: ReceiveChannel<String>
): String = select<String> {
channel1.onReceive {
it.also {
println(it)
}
}
channel2.onReceive {
it.also {
println(it)
}
}
}
repeat(3) {
selectChannel(channel1, channel2)
}
println("Time cost: ${System.currentTimeMillis() - startTime}")
}Log
a
b
c
Time cost: 570
Process finished with exit code 0
如果不知道Channel的個(gè)數(shù),如何避免ClosedReceiveChannelException?
使用:onReceiveCatching{}
runBlocking {
val startTime = System.currentTimeMillis()
val channel1 = produce<String> {
delay(5000L)
}
val channel2 = produce<String> {
delay(100L)
send("a")
delay(200L)
send("b")
delay(200L)
send("c")
}
suspend fun selectChannel(
channel1: ReceiveChannel<String>,
channel2: ReceiveChannel<String>
): String = select<String> {
channel1.onReceiveCatching {
it.getOrNull() ?: "channel1 is closed!"
}
channel2.onReceiveCatching {
it.getOrNull() ?: "channel2 is closed!"
}
}
repeat(6) {
val result = selectChannel(channel1, channel2)
println(result)
}
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
Log
a
b
c
channel2 is closed!
channel2 is closed!
channel2 is closed!
Time cost: 584
Process finished with exit code 0
得到所有結(jié)果以后,程序不會(huì)立即退出,因?yàn)?channel1 一直在 delay()。
所以我們需要在6次repeat之后將channel關(guān)閉。
runBlocking {
val startTime = System.currentTimeMillis()
val channel1 = produce<String> {
delay(15000L)
}
val channel2 = produce<String> {
delay(100L)
send("a")
delay(200L)
send("b")
delay(200L)
send("c")
}
suspend fun selectChannel(
channel1: ReceiveChannel<String>,
channel2: ReceiveChannel<String>
): String = select<String> {
channel1.onReceiveCatching {
it.getOrNull() ?: "channel1 is closed!"
}
channel2.onReceiveCatching {
it.getOrNull() ?: "channel2 is closed!"
}
}
repeat(6) {
val result = selectChannel(channel1, channel2)
println(result)
}
channel1.cancel()
channel2.cancel()
println("Time cost: ${System.currentTimeMillis() - startTime}")
}
Log
a
b
c
channel2 is closed!
channel2 is closed!
channel2 is closed!
Time cost: 612
Process finished with exit code 0
Deferred、Channel 的 API:
public interface Deferred : CoroutineContext.Element {
public suspend fun join()
public suspend fun await(): T
public val onJoin: SelectClause0
public val onAwait: SelectClause1<T>
}
public interface SendChannel<in E>
public suspend fun send(element: E)
public val onSend: SelectClause2<E, SendChannel<E>>
}
public interface ReceiveChannel<out E> {
public suspend fun receive(): E
public suspend fun receiveCatching(): ChannelResult<E>
public val onReceive: SelectClause1<E>
public val onReceiveCatching: SelectClause1<ChannelResult<E>>
}當(dāng) select 與 Deferred 結(jié)合使用的時(shí)候,當(dāng)并行的 Deferred 比較多的時(shí)候,你往往需要在得到一個(gè)最快的結(jié)果以后,去取消其他的 Deferred。
通過 async 并發(fā)執(zhí)行協(xié)程,也可以借助 select 得到最快的結(jié)果。
runBlocking {
suspend fun <T> fastest(vararg deferreds: Deferred<T>): T = select {
fun cancelAll() = deferreds.forEach {
it.cancel()
}
for (deferred in deferreds) {
deferred.onAwait {
cancelAll()
it
}
}
}
val deferred1 = async {
delay(100L)
println("done1")
"result1"
}
val deferred2 = async {
delay(200L)
println("done2")
"result2"
}
val deferred3 = async {
delay(300L)
println("done3")
"result3"
}
val deferred4 = async {
delay(400L)
println("done4")
"result4"
}
val deferred5 = async {
delay(5000L)
println("done5")
"result5"
}
val fastest = fastest(deferred1, deferred2, deferred3, deferred4, deferred5)
println(fastest)
}
Log
done1
result1
Process finished with exit code 0
到此這篇關(guān)于Kotlin select使用方法介紹的文章就介紹到這了,更多相關(guān)Kotlin select內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android獲取分享應(yīng)用列表詳解及實(shí)例
這篇文章主要介紹了Android獲取分享應(yīng)用列表詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04
Android編程判斷網(wǎng)絡(luò)連接是否可用的方法
這篇文章主要介紹了Android編程判斷網(wǎng)絡(luò)連接是否可用的方法,實(shí)例分析了Android判定網(wǎng)絡(luò)連接的相關(guān)技巧與實(shí)現(xiàn)步驟,需要的朋友可以參考下2015-12-12
9個(gè)非常棒的Android代碼編輯器 移動(dòng)開發(fā)者的最愛
這篇文章主要為大家分享了9個(gè)非常棒的Android代碼編輯器,據(jù)說這可是移動(dòng)開發(fā)者的最愛,知道是哪九個(gè)Android代碼編輯器2015-12-12
Android仿UC底部菜單欄實(shí)現(xiàn)原理與代碼
最近剛看完ViewPager,開始我打算用自定義的imgBtn,但是發(fā)現(xiàn)放在pager選項(xiàng)卡中不好排版,所以最好選了GridView,接下來介紹底部菜單欄實(shí)現(xiàn)2013-01-01
Android實(shí)現(xiàn)桌面快捷方式實(shí)例代碼
大家好,本篇文章主要講的是Android實(shí)現(xiàn)桌面快捷方式實(shí)例代碼,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
Android app啟動(dòng)時(shí)黑屏或者白屏的原因及解決辦法
這篇文章主要介紹了Android app啟動(dòng)時(shí)黑屏或者白屏的原因及解決辦法的相關(guān)資料,需要的朋友可以參考下2016-09-09
Android實(shí)現(xiàn)WebView刪除緩存的方法
這篇文章主要介紹了Android實(shí)現(xiàn)WebView刪除緩存的方法,實(shí)例分析了Android針對(duì)WebView操作緩存的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07

