本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本篇文章我们学习热流 Channel。Channel 是一种并发安全的队列,用来连接协程,实现协程间通信。
一、send() 发送数据,receive() 接收数据
与 Flow 不同的是,Channel 的 API 不是流式的,而是一端调用 send() 发送数据,一端调用 receive() 接收数据。
举个例子:
runBlocking {
val channel = Channel<Int>()
launch {
repeat(3) {
println("send: $it")
channel.send(it)
}
}
launch {
repeat(3) {
val result = channel.receive()
println("receive: $result")
}
}
}
在这个例子中,我们创建了一个 Channel 对象。然后开启一个协程,调用 send() 方法发射三次数据,再开启一个协程调用 receive() 方法接收三次数据。
运行程序,输出如下:
send: 0
receive: 0
send: 1
send: 2
receive: 1
receive: 2
可以看到,两个协程通过 Channel 完成了数据交换。
二、迭代器 Iterator
Channel 的本质是一个队列,可以通过迭代器访问其所有数据。
runBlocking {
val channel = Channel<Int>()
launch {
repeat(3) {
println("send: $it")
channel.send(it)
}
channel.close()
}
launch {
val iterator = channel.iterator()
while (iterator.hasNext()) {
val result = iterator.next()
println("receive: $result")
}
}
}
这里我们同样开启了一个协程,发射了三个数据,然后调用 close() 函数关闭 Channel。在另一个协程中,通过 channel.iterator() 获取到 Channel 的迭代器,并逐个访问 Channel 中的元素。
运行程序,输出如下:
send: 0
receive: 0
send: 1
send: 2
receive: 1
receive: 2
还可以通过 for-in 循环获取 Channel 中的所有元素:
runBlocking {
val channel = Channel<Int>()
launch {
repeat(3) {
println("send: $it")
channel.send(it)
}
channel.close()
}
launch {
for (element in channel) {
println("element: $element")
}
}
}
运行程序,输出如下:
send: 0
element: 0
send: 1
send: 2
element: 1
element: 2
三、Channel 缓冲区
Channel 的内部有一个缓冲区。如果缓冲区满了,receive() 方法还没有被调用,那么 send() 方法就会挂起,直到缓冲区中的元素被 receive() 函数取走后再继续执行。
Channel 默认缓冲区的大小是 0。
runBlocking {
val channel = Channel<Int>()
launch {
repeat(3) {
println("send: $it")
channel.send(it)
}
}
launch {
repeat(3) {
delay(1000)
val result = channel.receive()
println("receive: $result")
}
}
}
在这个例子中,第一个协程用来连续发射三个元素,第二个协程每隔 1s 接收一个元素。
运行程序,输出如下:
send: 0
receive: 0
send: 1
receive: 1
send: 2
receive: 2
可以看到,send() 方法并没有连续执行三次,而是在 receive() 函数被调用后才执行下一次。
这就是因为 Channel 的默认缓冲大小为 0,所以 send() 函数在发送一个元素后,就会挂起,直到 receive() 函数把发送的值消费掉再继续执行。
如果我们把缓冲区大小改为 2,运行结果如何呢?
runBlocking {
val startTime = System.currentTimeMillis()
val channel = Channel<Int>(2)
launch {
repeat(3) {
println("send: $it, time: ${System.currentTimeMillis() - startTime}")
channel.send(it)
}
}
launch {
repeat(3) {
delay(1000)
val result = channel.receive()
println("receive: $result, time: ${System.currentTimeMillis() - startTime}")
}
}
}
运行程序,输出如下:
send: 0, time: 39
send: 1, time: 42
send: 2, time: 42
receive: 0, time: 1056
receive: 1, time: 2061
receive: 2, time: 3066
可以看到,由于缓冲区中可以存储 2 个元素,加上等待区可以存储一个元素,所以三个元素都直接发送了。另一个协程中,每隔 1s 调用一次 receive() 方法,取出一个数据。
四、produce() 创建生产者协程
使用 produce() 函数可以快速创建一个生产者协程:
runBlocking {
val receiveChannel = produce {
repeat(3) {
send(it)
}
}
launch {
for (element in receiveChannel) {
println("$element")
}
}
}
这里我们通过 produce() 函数,创建出一个 ReceiveChannel,在这个 Channel 中发射了 3 个数据。
然后我们开启了另一个协程,用来读取其中的数据。
运行程序,输出如下:
0
1
2
五、actor() 创建消费者协程
使用 actor() 函数可以快速创建一个消费者协程:
runBlocking {
val sendChannel = actor<Int> {
repeat(3) {
val result = receive()
println("$result")
}
}
launch {
repeat(3) {
sendChannel.send(it)
}
}
}
}
}
这里我们通过 actor() 函数创建出一个 SendChannel,在这个 Channel 中,接收了 3 次数据。
然后我们开启了另一个协程,用来发射数据到 SendChannel 中。
运行程序,输出如下:
0
1
2
六、关闭 Channel
当 Channel 不再需要发送数据时,可以通过调用 close() 方法关闭 Channel。
当 close() 方法调用后,Channel 会立即停止发送新元素。但缓冲区中可能还有些元素没有处理完。在缓冲区中的元素处理完之前,Channel 的 receive() 方法仍然可用,直到缓冲区中的所有元素都被处理完后,Channel 才是真正意义上的关闭了。
Channel 有两个属性 isClosedForSend 和 isClosedForReceive。分别用来表示其是否关闭了发送和接收。
举个例子:
runBlocking {
val channel = Channel<Int>(3)
launch {
repeat(3) {
channel.send(it)
}
channel.close()
println("Channel closed, isClosedForSend = ${channel.isClosedForSend}, isClosedForReceive = ${channel.isClosedForReceive}")
}
launch {
delay(1000)
repeat(3) {
channel.receive()
}
println("All received, isClosedForSend = ${channel.isClosedForSend}, isClosedForReceive = ${channel.isClosedForReceive}")
}
}
在这个例子中,我们创建了一个缓冲区为 3 的 Channel,然后开启了一个协程,发射了 3 个数据。在另一个协程中,等待 1s 后,再接收 3 个数据。然后打印了 Channel 的 isClosedForSend 和 isClosedForReceive 属性。
运行程序,输出如下:
Channel closed, isClosedForSend = true, isClosedForReceive = false
All received, isClosedForSend = true, isClosedForReceive = true
可以看出,Channel 的 close() 方法调用后,isClosedForSend 马上变成了 true,但 isClosedForReceive 还是 false,因为此时缓冲区中还有元素。
当第二个协程把缓冲区中的所有元素取走后,isClosedForReceive 才变成 true。
produce() / actor() 创建的 Channel 会随着对应的协程执行完毕而关闭。
七、BroadcastChannel
普通的 Channel 中的元素只能被接收一次,如果需要多次接收,就需要用到 BroadcastChannel,BroadcastChannel 是一种广播机制的 Channel,属于观察者模式的运用。
runBlocking {
val broadcastChannel = BroadcastChannel<Int>(Channel.BUFFERED)
repeat(3) {
GlobalScope.launch {
val receiveChannel = broadcastChannel.openSubscription()
for (element in receiveChannel) {
println("coroutine $it received element: $element")
}
}
}
GlobalScope.launch {
repeat(3) {
broadcastChannel.send(it)
}
broadcastChannel.close()
}
delay(3000)
}
这里我们定义了一个 BroadcastChannel,然后开启了 3 个协程,通过 BroadcastChannel 的 openSubscription() 函数订阅其发送的数据。
运行程序,输出如下:
coroutine 2 received element: 0
coroutine 1 received element: 0
coroutine 2 received element: 1
coroutine 1 received element: 1
coroutine 0 received element: 0
coroutine 1 received element: 2
coroutine 0 received element: 1
coroutine 2 received element: 2
coroutine 0 received element: 2
可以看到,三个协程都收到了发出的数据。如果使用普通的 Channel,每个发出来的元素都只能被接收一次,无法达到全部通知的效果。
八、小结
本文我们学习了热流 Channel 的基本使用,包括其 send()、receive() 方法,可迭代访问的特性,缓冲区的概念。
然后介绍了 produce() 和 actor() 方法快速创建 Channel,这两个方法在实际开发中比较常用。
最后我们介绍了 BroadcastChannel,它用于处理一对多的关系,也就是观察者模式中的被观察者通知所有观察者。