注:本文中使用 runBlocking 是為了方便測試,業(yè)務(wù)開發(fā)中禁止使用
一、Channel 基礎(chǔ)
(1)Channel 翻譯過來為通道或者管道,實際上就是個隊列, 是一個面向多協(xié)程之間數(shù)據(jù)傳輸?shù)?BlockQueue
,用于協(xié)程間通信;
(2)Channel 使用 send
和 receive
兩個方法往管道里面寫入和讀取數(shù)據(jù),這兩個方法是非阻塞的掛起函數(shù);
(3)Channel 是熱流,不管有沒有訂閱者,上游都會發(fā)射數(shù)據(jù)。
1、簡單使用
fun channelFun() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(5) {
println(channel.receive())
}
println("Done!")
// 1
// 4
// 9
// 16
// 25
// Done!
}
2、Channel 的迭代
(1)我們發(fā)現(xiàn),這種方式,實際上是我們一直在等待讀取 Channel 中的數(shù)據(jù),只要有數(shù)據(jù)到了,就會被讀取到;
(2)最后一行 Done! 沒有打印出來,表示程序沒有結(jié)束,一直處于等待讀取數(shù)據(jù)的狀態(tài)。
fun channelIteratorFun() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
channel.send(x * x)
}
}
/*val iterator = channel.iterator()
while (iterator.hasNext()) {
println(iterator.next())
}*/
for (y in channel) {
println(y)
}
println("Done")
// 1
// 4
// 9
// 16
// 25
}
3、關(guān)閉 Channel
(1)調(diào)用 close
方法就像向通道發(fā)送了一個特殊的關(guān)閉指令,這個迭代停止,說明關(guān)閉指令已經(jīng)被接收了;
(2)這里能夠保證所有先前發(fā)送出去的元素都能在通道關(guān)閉前被接收到;
(3)調(diào)用了 close
會立即停止接受新元素,isClosedForSend
會立即返回 true
,而由于 Channel
緩沖區(qū)的存在,這時候可能還有一些元素沒有被處理完,所以要等所有的元素都被讀取之后 isClosedForReceive
才會返回 true
。
fun channelCloseFun() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
channel.send(x * x)
}
channel.close() //結(jié)束發(fā)送數(shù)據(jù)
}
for (y in channel) {
println(y)
}
println("Done!")
// 1
// 4
// 9
// 16
// 25
// Done!
}
4、Channel 的類型
(1)Channel
是一個接口,它繼承了 SendChannel
和 ReceiveChannel
兩個接口
(2)SendChannel
提供了發(fā)射數(shù)據(jù)的功能,有如下重點接口:
??- send
是一個掛起函數(shù),將指定的元素發(fā)送到此通道,在該通道的緩沖區(qū)已滿或不存在時掛起調(diào)用者。如果通道已經(jīng)關(guān)閉,調(diào)用發(fā)送時會拋出異常;
??- trySend
如果不違反其容量限制,則立即將指定元素添加到此通道,并返回成功。否則,返回失敗或關(guān)閉;
??- close
關(guān)閉通道;
??- isClosedForSend
判斷通道是否已經(jīng)關(guān)閉,如果關(guān)閉,調(diào)用 send 會引發(fā)異常。
(3)ReceiveChannel
提供了接收數(shù)據(jù)的功能,有如下重點接口:
?? - receive
如果此通道不為空,則從中檢索并刪除元素;如果通道為空,則掛起調(diào)用者;如果通道未接收而關(guān)閉,則引發(fā) ClosedReceiveChannel 異常;
?? - tryReceive
如果此通道不為空,則從中檢索并刪除元素,返回成功結(jié)果;如果通道為空,則返回失敗結(jié)果;如果通道關(guān)閉,則返回關(guān)閉結(jié)果;
?? - receiveCatching
如果此通道不為空,則從中檢索并刪除元素,返回成功結(jié)果;如果通道為空,則返回失敗結(jié)果;如果通道關(guān)閉,則返回關(guān)閉的原因;
?? - isEmpty
判斷通道是否為空;
?? - isClosedForReceive
判斷通道是否已經(jīng)關(guān)閉,如果關(guān)閉,調(diào)用 receive 會引發(fā)異常;
?? - cancel(cause: CancellationException? = null)
以可選原因取消接收此頻道的剩余元素,此函數(shù)用于關(guān)閉通道并從中刪除所有緩沖發(fā)送的元素;
?? - iterator()
返回通道的迭代器。
(4)創(chuàng)建不同類型的 Channel
?? - Rendezvous channel
0尺寸 buffer (默認類型)
?? - Unlimited channel
無限元素, send 不被掛起
?? - Buffered channel
指定大小, 滿了之后 send 掛起
?? - Conflated channel
新元素會覆蓋舊元素, receiver 只會得到最新元素, send 永不掛起
fun channelCreateFun() = runBlocking {
val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(Channel.CONFLATED)
val unlimitedChannel = Channel<String>(Channel.UNLIMITED)
}
二、Channel實現(xiàn)協(xié)程間通信
1、多個協(xié)程訪問同一個 Channel
fun multipleCoroutineFun() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..3) {
channel.send(x)
}
}
launch {
delay(10)
for (y in channel) {
println("1 --> $y")
}
}
launch {
delay(20)
for (y in channel) {
println("2 --> $y")
}
}
launch {
delay(30)
for (x in 90..93) {
channel.send(x)
}
channel.close()
}
delay(1000)
println("Done!")
// 1 --> 1
// 2 --> 3
// 1 --> 2
// 2 --> 90
// 2 --> 92
// 1 --> 91
// 2 --> 93
// Done!
}
2、produce 和 actor
(1)通過 produce
這個方法啟動一個生產(chǎn)者協(xié)程,并返回一個 ReceiveChannel
,其他協(xié)程就可以拿著這個 Channel 來接收數(shù)據(jù)了;
(2)通過 actor
可以用來構(gòu)建一個消費者協(xié)程,并返回一個 SendChannel
,其他協(xié)程就可以拿著這個 Channel 來發(fā)送數(shù)據(jù)了。
fun produceFun() = runBlocking {
val receiveChannel = produce {
for (x in 1..3) {
delay(500)
send(x)
}
}
for (x in receiveChannel) {
println(x)
}
delay(3000)
receiveChannel.cancel()
println("Done!")
// 1
// 2
// 3
// Done!
}
fun actorFun() = runBlocking {
val sendChannel = actor<Int> {
for (e in channel) {
println(e)
}
}
sendChannel.send(100)
delay(2000)
sendChannel.close()
println("Done!")
// 100
// Done!
}
3、BroadcastChannel
(1)BroadcastChannel
被標記為過時了,請使用 SharedFlow
和 StateFlow
替代它;
(2)1中例子提到一對多的情形,從數(shù)據(jù)處理本身來講,有多個接收端的時候,同一個元素只會被一個接收端讀到;而 BroadcastChannel
則不然,多個接收端不存在互斥現(xiàn)象。
fun broadcastChannelFun() = runBlocking {
val broadcastChannel = BroadcastChannel<Int>(5)
val receiveChannel1 = broadcastChannel.openSubscription()
val receiveChannel2 = broadcastChannel.openSubscription()
launch {
for (x in 1..3) {
broadcastChannel.send(x)
}
}
launch {
for (e in receiveChannel1) {
println("1 --> $e")
}
}
launch {
for (e in receiveChannel2) {
println("2 --> $e")
}
}
delay(1000)
broadcastChannel.close()
println("Done!")
// 1 --> 1
// 1 --> 2
// 1 --> 3
// 2 --> 1
// 2 --> 2
// 2 --> 3
// Done!
}
使用 broadcast()
擴展函數(shù)可以將 Channel
轉(zhuǎn)換成 BroadcastChannel
fun broadcastChannelFun2() = runBlocking {
val channel = Channel<Int>()
val broadcastChannel = channel.broadcast(3)
val receiveChannel1 = broadcastChannel.openSubscription()
val receiveChannel2 = broadcastChannel.openSubscription()
launch {
for (x in 1..3) {
channel.send(x)
}
}
launch {
for (e in receiveChannel1) {
println("1 --> $e")
}
}
launch {
for (e in receiveChannel2) {
println("2 --> $e")
}
}
delay(1000)
channel.close()
println("Done!")
// 1 --> 1
// 1 --> 2
// 1 --> 3
// 2 --> 1
// 2 --> 2
// 2 --> 3
// Done!
}