為什麼需要 Channel#
到目前為止我們都是用 launch、async 建立 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(): Ereceive 的行為#
當 channel 中還有資料時,receive 會直接取出;如果沒有,呼叫端的 coroutine 就會被暫停,直到 channel 又被塞入新的元素。
send 的行為#
send 同樣是 suspend 函式:
public suspend fun send(element: E)不過在預設情況下,send 並不會主動暫停。原因是 Channel() 不帶任何參數時,建立出來的是沒有緩衝區(Buffer)的 Rendezvous Channel。
Rendezvous Channel 的行為是:當 send 與 receive 同時被呼叫時,資料才會交換。因為沒有 Buffer,所以也不需要把元素暫存起來,send 在這種情況下就不需要暫停。
小結#
- Channel 是 coroutine 之間傳遞資料的通道,採用 FIFO。
- 預設
Channel()的容量(Capacity)是 0,也就是沒有 Buffer。 - 此時
send與receive必須成雙成對地配合:呼叫次數不一致時,多出來的那一方就會暫停。 - 接下來會介紹其他種類的 Channel,看看不同 capacity 設定會帶來什麼差異。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10268686