為什麼需要 Channel#

到目前為止我們都是用 launchasync 建立 CoroutineScope 來啟動 coroutine。這些 builder 有一個共通點:必須等到任務完整結束之後,外部才拿得到結果。

但實務上常常會遇到另一種需求:

  • 在一個 coroutine 中需要處理多項任務。
  • 不希望全部任務跑完才一次回傳。
  • 希望每完成一項,就把結果立刻送出去。

Kotlin Coroutine 提供 Channel(通道)來處理這類情境。Channel 就像兩個 CoroutineScope 之間架起的一條通道,資料可以從一端送進去,從另一端取出來。

第一個 Channel 範例#

下面這段程式建立一個 Channel<Int>,內部 coroutine 每隔 200 毫秒送出一個整數,外層則重複 10 次把資料取出。

class Day15 {
    val scope = CoroutineScope(Job())

    suspend fun firstChannel() {
        val channel = Channel<Int>()
        scope.launch {
            repeat(10) {
                delay(200)
                channel.send(it)
            }
        }
        repeat(10) {
            println(channel.receive())
        }
        println("Finish!")
    }
}

從這個例子可以歸納出幾個重點:

  • Channel 可以橫跨不同的 CoroutineScope(scope.launch 與外層)傳遞資料。
  • 送資料用 send,取資料用 receive
  • Channel 採取 FIFO(First in first out)的順序,先送進去的會先被取出。

send 與 receive 的暫停行為#

如果把 receive 多呼叫一次,例如 repeat(11)

class Day15 {
    val scope = CoroutineScope(Job())

    suspend fun firstChannel() {
        val channel = Channel<Int>()
        scope.launch {
            repeat(10) {
                delay(200)
                channel.send(it)
            }
        }
        repeat(11) {
            println(channel.receive())
        }
        println("Finish!")
    }
}

執行結果是 coroutine 不會結束,第 11 次的 receive 會持續等待。原因是 receive 本身就是一個 suspend 函式:

public suspend fun receive(): E

receive 的行為#

當 channel 中還有資料時,receive 會直接取出;如果沒有,呼叫端的 coroutine 就會被暫停,直到 channel 又被塞入新的元素。

send 的行為#

send 同樣是 suspend 函式:

public suspend fun send(element: E)

不過在預設情況下,send 並不會主動暫停。原因是 Channel() 不帶任何參數時,建立出來的是沒有緩衝區(Buffer)的 Rendezvous Channel。

Rendezvous Channel 的行為是:當 sendreceive 同時被呼叫時,資料才會交換。因為沒有 Buffer,所以也不需要把元素暫存起來,send 在這種情況下就不需要暫停。

小結#

  • Channel 是 coroutine 之間傳遞資料的通道,採用 FIFO。
  • 預設 Channel() 的容量(Capacity)是 0,也就是沒有 Buffer。
  • 此時 sendreceive 必須成雙成對地配合:呼叫次數不一致時,多出來的那一方就會暫停。
  • 接下來會介紹其他種類的 Channel,看看不同 capacity 設定會帶來什麼差異。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10268686