Kotlin?select使用方法介紹
一、select是什么
select——>用于選擇更快的結(jié)果。
基于場(chǎng)景理解
比如客戶(hù)端要查詢(xún)一個(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
上述程序分為四步:第一步:查詢(xún)緩存信息;第二步:緩存服務(wù)返回信息,更新 UI;第三步:查詢(xún)網(wǎng)絡(luò)服務(wù);第四步:網(wǎng)絡(luò)服務(wù)返回信息,更新 UI。
用戶(hù)可以第一時(shí)間看到商品的信息,雖然它暫時(shí)會(huì)展示舊的信息,但由于我們同時(shí)查詢(xún)了網(wǎng)絡(luò)服務(wù),舊緩存信息也馬上會(huì)被替代成新的信息。但是可能存在一些問(wèn)題:如果程序卡在了緩存服務(wù),獲取網(wǎng)絡(luò)服務(wù)就會(huì)無(wú)法執(zhí)行。是因?yàn)?getCacheInfo() 它是一個(gè)掛起函數(shù),只有這個(gè)程序執(zhí)行成功以后,才可以繼續(xù)執(zhí)行后面的任務(wù)。能否做到:兩個(gè)掛起函數(shù)同時(shí)執(zhí)行,誰(shuí)返回的速度更快,就選擇哪個(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)問(wèn)題的時(shí)候,靈活選擇網(wǎng)絡(luò)服務(wù)的結(jié)果。從而避免用戶(hù)等待太長(zhǎng)的時(shí)間,得到糟糕的體驗(yàn)。
在上述代碼中,用戶(hù)大概率是會(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),通過(guò)這個(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。
通過(guò) 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)文章
Flutter使用Overlay與ColorFiltered新手引導(dǎo)實(shí)現(xiàn)示例
這篇文章主要介紹了Flutter使用Overlay與ColorFiltered新手引導(dǎo)實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android開(kāi)發(fā)之TextView使用intent傳遞信息,實(shí)現(xiàn)注冊(cè)界面功能示例
這篇文章主要介紹了Android開(kāi)發(fā)之TextView使用intent傳遞信息,實(shí)現(xiàn)注冊(cè)界面功能,涉及Android使用intent傳值及界面布局等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04Android HttpURLConnection斷點(diǎn)下載(單線(xiàn)程)
這篇文章主要為大家詳細(xì)介紹了Android HttpURLConnection斷點(diǎn)下載的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Android基于ViewPager實(shí)現(xiàn)的應(yīng)用歡迎界面完整實(shí)例
這篇文章主要介紹了Android基于ViewPager實(shí)現(xiàn)的應(yīng)用歡迎界面,結(jié)合完整實(shí)例形式分析了ViewPager類(lèi)用于歡迎界面顯示圖片的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-08-08詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑
本篇文章主要介紹了詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Android右滑返回上一個(gè)界面的實(shí)現(xiàn)方法
這篇文章主要介紹了Android右滑返回上一個(gè)界面的實(shí)現(xiàn)方法的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10Android Okhttp請(qǐng)求查詢(xún)購(gòu)物車(chē)的實(shí)例代碼
下面小編就為大家分享一篇Android Okhttp請(qǐng)求查詢(xún)購(gòu)物車(chē)的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01android7.0實(shí)現(xiàn)分享圖片到朋友圈功能
這篇文章主要為大家詳細(xì)介紹了android7.0實(shí)現(xiàn)分享圖片到朋友圈功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Android實(shí)現(xiàn)彈出鍵盤(pán)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)彈出鍵盤(pán)的方法,是非常實(shí)用的技巧,需要的朋友可以參考下2014-09-09