android kotlin 協程(四) 協程間的通信

語言: CN / TW / HK

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

先來看個最簡單的例子

image-20230217133712320

可以看出,A協程可以完成發送,並且B協程也可以完成接受

如果説A協程是一個網絡接口,會返回數據,此時B協程是否還會等待A協程數據返回呢?

image-20230217134057270

可以看出,即使是A協程會延遲2s,那麼B協程也會等待A協程返回

如果説,A協程現在有3條數據要發送,B協程是否會接受3條呢?

image-20230217134602704

那麼就要介紹 Channel()的第一個參數了:

  • capacity 通道容量

channel 類似於一個阻塞隊列(BlockingQueue), 默認是隻緩存1條數據,只要不取,那麼新的數據就無法加入到容器中

當send第二條數據的時候, 發現並沒有receive() 來取第二條數據,所以就會出現一直掛起的效果

此時我們只需要讓channel通道中容量變大,多存放幾條數據即可

例如這樣:

image-20230217142836865

如果説,我們不想改變通道容量的大小,並且, 還要不讓他掛起,那麼就要介紹 channel的第二個參數了:

  • onBufferOverflow

從名字也可以看出,這是緩衝區溢出策略,一共有三種狀態

  • BufferOverflow.SUSPEND: 掛起策略,當send不進去數據的時候,始終掛起,等待 receive() [默認]
  • BufferOverflow.DROP_OLDEST: 當要溢出的時候,刪除緩衝區中最舊的值
  • BufferOverflow.DROP_LASTEST: 當要溢出的時候,刪除緩衝區中最新的值

還是上面的例子,我們將容量設置為1, 往 channel中send 3條數據來看看效果

| BufferOverflow.DROP_OLDEST | BufferOverflow.DROP_LASTEST | | ------------------------------------------------------------ | ------------------------------------------------------------ | | image-20230217170914395 | image-20230217171031687 |

目前這些代碼應該很好理解!

trySend / tryReceive

在新版的channel更新的api中,還增添了一系列 tryXXapi

來看一段代碼:

image-20230218134149723

  • trySend() 嘗試向channel中發送數據。這個函數會立即返回一個結果,表明是否成功將元素髮送到通道中。如果通道已滿,它會立即返回一個Failure類型的結果,否則會返回一個Success類型的結果。一般來説,生產者協程使用 trySend 函數來嘗試將數據發送到通道中,不會阻塞協程,同時可以通過返回結果來判斷是否成功發送數據。
  • tryReceive() 這個函數會立即返回一個結果,表明是否成功從通道中接收到元素。如果通道已空,它會立即返回一個Failure類型的結果,否則會返回一個Success類型的結果。一般來説,消費者協程使用 tryReceive 函數來嘗試從通道中接收數據,不會阻塞協程,同時可以通過返回結果來判斷是否成功接收數據。

```kotlin // TODO =================== trySend / tryReceive ====================== fun main() = runBlocking { // 用來協程間的通信 val channel = Channel(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) println("main start")

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")

} ```

運行結果:

image-20230220133929092

還有一些比較老的方法例如:

  • offer / poll 等一些淘汰的方法就不説了,

onSend / onReceive

還有最後一種發送,獲取數據的方式,這種方式是通過select 選擇器來實現的,先來看代碼

```kotlin //// TODO =================== onSend / onReceive ====================== fun main() = runBlocking { // 用來協程間的通信 val channel = Channel(capacity = 5, onBufferOverflow = BufferOverflow.SUSPEND) println("main start")

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")

} ```

運行結果:

image-20230218152701375

select作用不止這些,目前瞭解可以接受即可,下面會重點提到!

這裏有一個小知識:

如果看到有這種Select開頭的,基本都是要寫到select{} 中才能使用

image-20230218150450649

運行結果:

image-20230218150240659

select 下面會提到,這裏就不重點説了.

在實際開發中,對於我來説,channel用的還是比較少, 我感覺這玩意比較坑,一般情況下,要實現2個協程通信,我會採用flow

例如這樣:

image-20230220151404613

這篇重點不是flow,這裏就不多説了!

produce / actor

produce

produce意為生產者, 其本質就是對協程和channel的一層封裝,

它返回一個 ReceiveChannel 對象,這個對象可以用於在其他協程中消費 生產者協程產生的數據。

使用很簡單

image-20230218153514189:

api還是調用的channel的,對我們來説只是省略了, new Channel() 的過程,這裏就不多説了

actor

actor 與produce正好相反

actor本質也是對協程與channel的封裝, 它會返回一個SendChannel對象,這個對象用來給協程體發送數據

image-20230220135911322

select

定義: 一旦某個掛起函數返回結果,select 結構就會立即返回該函數的結果,而其他仍在等待的掛起函數將會被取消。

注意點: select 只能用於掛起函數(即使用 suspend 修飾的函數)。另外,select 的選擇器表達式中每個分支都應該返回相同類型的值,否則會編譯報錯。

簡單的説就是, select 可以找到哪一個協程執行最快, 吧執行最快的結果返回,其他執行慢的,或者沒有執行的協程全部關閉!

假設我們現在有一個實際的應用場景:

在實際開發中,我們需要請求接口, 請求接口的之前需要判斷是否有緩存,

如果有緩存,就使用緩存數據

但是,如果請求接口比讀取緩存數據還快,那麼我們就用請求出來的數據

一般情況下緩存永遠比請求數據快,這裏就舉個例子

image-20230220142302331

select不僅可以監聽 async的返回, 還有很多用處,例如可以監聽協程是否執行完, 並且返回最快執行完的協程

來看看代碼:

image-20230220143320288

完整代碼

下篇預告:

  • suspendCoroutine{}
  • suspendCancellableCoroutine{}
  • suspend 與 continuation與狀態機器
  • 不通過協程運行 suspend函數

原創不易,您的點贊就是對我最大的幫助!