Select 並非什麼新鮮概念,咱們在 IO 多路複用的時候就見過它,在 Java NIO 裏面也見過它。接下來給各位介紹的是 Kotlin 協程的 Select。java
咱們前面已經接觸了不少掛起函數,那麼若是我有這樣一個場景,兩個 API 分別從網絡和本地緩存獲取數據,指望哪一個先返回就先用哪一個作展現:git
fun CoroutineScope.getUserFromApi(login: String) = async(Dispatchers.IO){
gitHubServiceApi.getUserSuspend(login)
}
fun CoroutineScope.getUserFromLocal(login:String) = async(Dispatchers.IO){
File(localDir, login).takeIf { it.exists() }?.readText()?.let { gson.fromJson(it, User::class.java) }
}
複製代碼
無論先調用哪一個 API 返回的 Deferred
的 await
,都會被掛起,若是想要實現這一需求就要啓動兩個協程來調用 await
,這樣反而將問題複雜化了。編程
接下來咱們用 select
來解決這個問題:緩存
GlobalScope.launch {
val localDeferred = getUserFromLocal(login)
val remoteDeferred = getUserFromApi(login)
val userResponse = select<Response<User?>> {
localDeferred.onAwait { Response(it, true) }
remoteDeferred.onAwait { Response(it, false) }
}
...
}.join()
複製代碼
你們能夠看到,咱們沒有直接調用 await
,而是調用了 onAwait
在 select
當中註冊了個回調,無論哪一個先回調,select
當即返回對應回調中的結果。假設 localDeferred.onAwait
先返回,那麼 userResponse
的值就是 Response(it, true)
,固然因爲咱們的本地緩存可能不存在,所以 select
的結果類型是 Response<User?>
。網絡
對於這個案例自己,若是先返回的是本地緩存,那麼咱們還須要獲取網絡結果來展現最新結果:async
GlobalScope.launch {
...
userResponse.value?.let { log(it) }
userResponse.isLocal.takeIf { it }?.let {
val userFromApi = remoteDeferred.await()
cacheUser(login, userFromApi)
log(userFromApi)
}
}.join()
複製代碼
對於多個 Channel
的狀況,也比較相似:函數
val channels = List(10) { Channel<Int>() }
select<Int?> {
channels.forEach { channel ->
channel.onReceive { it }
// OR
channel.onReceiveOrNull { it }
}
}
複製代碼
對於 onReceive
,若是 Channel
被關閉,select
會直接拋出異常;而對於 onReceiveOrNull
若是遇到 Channel
被關閉的狀況,it
的值就是 null
。post
咱們怎麼知道哪些事件能夠被 select
呢?其實全部可以被 select
的事件都是 SelectClauseN
類型,包括:學習
SelectClause0
:對應事件沒有返回值,例如 join
沒有返回值,對應的 onJoin
就是這個類型,使用時 onJoin
的參數是一個無參函數:select<Unit> {
job.onJoin { log("Join resumed!") }
}
複製代碼
SelectClause1
:對應事件有返回值,前面的 onAwait
和 onReceive
都是此類狀況。SelectClause2
:對應事件有返回值,此外還須要額外的一個參數,例如 Channel.onSend
有兩個參數,第一個就是一個 Channel
數據類型的值,表示即將發送的值,第二個是發送成功時的回調:List(100) { element ->
select<Unit> {
channels.forEach { channel ->
channel.onSend(element) { sentChannel -> log("sent on $sentChannel") }
}
}
}
複製代碼
在消費者的消費效率較低時,數據能發給哪一個就發給哪一個進行處理,onSend
的第二個參數的參數是數據成功發送到的 Channel
對象。所以若是你們想要確認掛起函數是否支持 select
,只須要查看其是否存在對應的 SelectClauseN
便可。ui
在協程當中,Select 的語義與 Java NIO 或者 Unix 的 IO 多路複用相似,它的存在使得咱們能夠輕鬆實現 1 拖 N,實現哪一個先來就處理哪一個。儘管 Select 和 Channel 比起標準庫的協程 API 已經更接近業務開發了,不過我的認爲它們仍屬於相對底層的 API 封裝,在實踐當中多數狀況下也可使用 Flow API 來解決。
而這個 Flow API,徹底就是響應式編程的協程版 API,咱們簡直能夠照着 RxJava 來學習它,因此咱們下一篇再見吧~~~
歡迎關注 Kotlin 中文社區!
中文官網:www.kotlincn.net/
中文官方博客:www.kotliner.cn/
公衆號:Kotlin
知乎專欄:Kotlin
CSDN:Kotlin中文社區
掘金:Kotlin中文社區
簡書:Kotlin中文社區