android kotlin 協程(四) 協程間的通訊
theme: github
android kotlin 協程(四) 協程間的通訊
學完本篇你將會了解到:
- channel
- produce
- actor
- select
先來通過上一篇的簡單案例回顧一下掛起於恢復:
```kotlin
fun main() {
val waitTime = measureTimeMillis {
runBlocking
println("main start") // 1 // 排程前
launch {
println("launch 1 start") // 2 // 排程後(執行前)
delay(1000) // 延遲1s (不會阻塞兄弟協程)
println("launch 1 end") // 3
}
println("main mid") // 4 // 排程前
launch {
println("launch 2 start") // 5 // 排程後執行
delay(500) // 延遲0.5s (不會阻塞兄弟協程)
println("launch 2 end") // 6
}
println("main end") // 7 // 排程前
}
}
println("等待時間:${waitTime}")
} ```
通過上一篇我們知道了在協程中,
是會先執行排程前的程式碼,然後會執行排程後的程式碼, 直到排程後的時候,才會真正的執行到協程體中
所以這段程式碼的執行順序為:
1,4,7,2,5,6,3
launch{} 中的lambda表示式 是一個suspend 函式標記的,所以始終是非同步的,並不會阻塞兄弟協程
所以等待時間 約等於 1000
這裡為什麼說是約等於呢? 因為建立協程等一系列操作會稍微耗時一點,直接取整即可!
Channel
send / receive
channel是用來協程之前通訊的,例如現在有一個需求,B協程需要使用A協程中的某個值,那麼就用到了channel
先來看個最簡單的例子
可以看出,A協程可以完成傳送,並且B協程也可以完成接受
如果說A協程是一個網路介面,會返回資料,此時B協程是否還會等待A協程資料返回呢?
可以看出,即使是A協程會延遲2s,那麼B協程也會等待A協程返回
如果說,A協程現在有3條資料要傳送,B協程是否會接受3條呢?
那麼就要介紹 Channel()的第一個引數了:
- capacity 通道容量
channel 類似於一個阻塞佇列(BlockingQueue), 預設是隻快取1條資料,只要不取,那麼新的資料就無法加入到容器中
當send第二條資料的時候, 發現並沒有receive() 來取第二條資料,所以就會出現一直掛起的效果
此時我們只需要讓channel通道中容量變大,多存放幾條資料即可
例如這樣:
如果說,我們不想改變通道容量的大小,並且, 還要不讓他掛起,那麼就要介紹 channel的第二個引數了:
- onBufferOverflow
從名字也可以看出,這是緩衝區溢位策略,一共有三種狀態
- BufferOverflow.SUSPEND: 掛起策略,當send不進去資料的時候,始終掛起,等待 receive() [預設]
- BufferOverflow.DROP_OLDEST: 當要溢位的時候,刪除緩衝區中最舊的值
- BufferOverflow.DROP_LASTEST: 當要溢位的時候,刪除緩衝區中最新的值
還是上面的例子,我們將容量設定為1, 往 channel中send 3條資料來看看效果
| BufferOverflow.DROP_OLDEST | BufferOverflow.DROP_LASTEST |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| |
|
目前這些程式碼應該很好理解!
trySend / tryReceive
在新版的channel更新的api中,還增添了一系列 tryXXapi
來看一段程式碼:
- trySend() 嘗試向channel中傳送資料。這個函式會立即返回一個結果,表明是否成功將元素髮送到通道中。如果通道已滿,它會立即返回一個
Failure
型別的結果,否則會返回一個Success
型別的結果。一般來說,生產者協程使用trySend
函式來嘗試將資料傳送到通道中,不會阻塞協程,同時可以通過返回結果來判斷是否成功傳送資料。 - tryReceive() 這個函式會立即返回一個結果,表明是否成功從通道中接收到元素。如果通道已空,它會立即返回一個
Failure
型別的結果,否則會返回一個Success
型別的結果。一般來說,消費者協程使用tryReceive
函式來嘗試從通道中接收資料,不會阻塞協程,同時可以通過返回結果來判斷是否成功接收資料。
```kotlin
// TODO =================== trySend / tryReceive ======================
fun main() = runBlocking
launch { // A協程
// channel.close() val trySend = channel.trySend("A協程傳送資料 1") if (trySend.isSuccess) { println("channel 傳送成功") } else if (trySend.isClosed) { println("channel 關閉了") } else if (trySend.isFailure) { println("channel 傳送失敗") } }.join() // A協程必須執行完,通道有資料了之後才能取
val tryReceive = channel.tryReceive()
if (tryReceive.isSuccess) {
println("tryReceive 接收到了資料:${tryReceive.getOrNull()}")
} else if (tryReceive.isClosed) {
println("tryReceive 關閉了")
} else if (tryReceive.isFailure) {
println("tryReceive 傳送失敗")
}
println("main end")
} ```
執行結果:
還有一些比較老的方法例如:
- offer / poll 等一些淘汰的方法就不說了,
onSend / onReceive
還有最後一種傳送,獲取資料的方式,這種方式是通過select 選擇器來實現的,先來看程式碼
```kotlin
//// TODO =================== onSend / onReceive ======================
fun main() = runBlocking
launch { // A 協程 傳送資料
channel.send("send傳送資料 ")
channel.trySend("trySend傳送資料")
}
// select 接收資料 預設會掛起,等待資料返回
select {
channel.onReceive {
println("onReceive:$it")
}
}
println("result ${channel.receive()}")
channel.invokeOnClose {
println("channel close ")
}
println("main end")
} ```
執行結果:
select作用不止這些,目前瞭解可以接受即可,下面會重點提到!
這裡有一個小知識:
如果看到有這種Select開頭的,基本都是要寫到select{} 中才能使用
執行結果:
select 下面會提到,這裡就不重點說了.
在實際開發中,對於我來說,channel用的還是比較少, 我感覺這玩意比較坑,一般情況下,要實現2個協程通訊,我會採用flow
例如這樣:
這篇重點不是flow,這裡就不多說了!
produce / actor
produce
produce意為生產者, 其本質就是對協程和channel的一層封裝,
它返回一個 ReceiveChannel
物件,這個物件可以用於在其他協程中消費 生產者協程產生的資料。
使用很簡單
:
api還是呼叫的channel的,對我們來說只是省略了, new Channel() 的過程,這裡就不多說了
actor
actor 與produce正好相反
actor本質也是對協程與channel的封裝, 它會返回一個SendChannel
物件,這個物件用來給協程體傳送資料
select
定義: 一旦某個掛起函式返回結果,select
結構就會立即返回該函式的結果,而其他仍在等待的掛起函式將會被取消。
注意點: select
只能用於掛起函式(即使用 suspend
修飾的函式)。另外,select
的選擇器表示式中每個分支都應該返回相同型別的值,否則會編譯報錯。
簡單的說就是, select 可以找到哪一個協程執行最快, 吧執行最快的結果返回,其他執行慢的,或者沒有執行的協程全部關閉!
假設我們現在有一個實際的應用場景:
在實際開發中,我們需要請求介面, 請求介面的之前需要判斷是否有快取,
如果有快取,就使用快取資料
但是,如果請求介面比讀取快取資料還快,那麼我們就用請求出來的資料
一般情況下快取永遠比請求資料快,這裡就舉個例子
select不僅可以監聽 async的返回, 還有很多用處,例如可以監聽協程是否執行完, 並且返回最快執行完的協程
來看看程式碼:
下篇預告:
- suspendCoroutine{}
- suspendCancellableCoroutine{}
- suspend 與 continuation與狀態機器
- 不通過協程執行 suspend函式
原創不易,您的點贊就是對我最大的幫助!