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函式

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