本文基于协程官方文档讲解,具体可查看here
channel主要用来协程间通信。
一、挂起函数send和receive
send和receive不匹配的情形:👇
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(4) { //只接收4次
if (!channel.isClosedForReceive) {
printMsg("${channel.receive()}")
}
}
printMsg("Done")
}
这里发送了5次,接收只有4次,导致send挂起。程序不能正常结束,很危险。
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(6) { //多接收一次
if (!channel.isClosedForReceive) {
printMsg("${channel.receive()}")
}
}
printMsg("Done")
}
这里发送了5次,接收6次,导致receive挂起。程序不能正常结束,很危险。
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x * x)
}
repeat(5) {
if (!channel.isClosedForReceive) {
printMsg("${channel.receive()}")
}
}
printMsg("Done")
}
发送和接收是匹配的,程序正常结束。
Q:我们一般用完channel,就让其关闭的,但是接收和发送不匹配时,会一直挂起,程序没法结束,咋办?
可以在合适的时机主动close。
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
if (!channel.isClosedForSend) {
channel.send(x * x)
}
if (x > 3) {
channel.close()
break
}
}
}
repeat(7) {
printMsg("${channel.receiveCatching().getOrNull()}")
}
printMsg("Done")
}
多出了两次receive,我们可以添加判断解决channel.isClosedForReceive。
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
if (!channel.isClosedForSend) {
channel.send(x * x)
}
if (x > 3) {
channel.close()
break
}
}
}
repeat(7) {
if(!channel.isClosedForReceive){ //加多一个判断
printMsg("${channel.receiveCatching().getOrNull()}")
}
}
printMsg("Done")
}
当遇到channel关闭时,使用receive接收数据尤其小心,可能会导致奔溃,建议使用channel.receiveCatching().getOrNull()
二、channel支持迭代接收
fun main() = runBlocking {
val channel = Channel<Int>()
launch {
for (x in 1..5) {
channel.send(x * x)
if (x > 3) {
channel.close()
break
}
}
}
for (y in channel) { //支持迭代遍历接收
printMsg("${y}")
}
printMsg("Done")
}
当然这里的迭代也可以用
consumeEach。
//接收
channel.consumeEach {
printMsg("${it}")
}
三、使用produce构建channel
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
for (x in 1..5) send(x * x)
}
fun main() = runBlocking {
val squares = produceSquares()
squares.consumeEach { printMsg("${it}") }
printMsg("Done")
}
四、ReceiveChannel再次处理
一个协程产生多个流式值,另外一个协程可以对其进行处理,产生新值,最后可以消费。
//一个协程产生很多流式的值
fun CoroutineScope.produceNumbers(): ReceiveChannel<Int> = produce {
var x = 1
while (true) send(x++)
}
//另外一个协程对其进行处理,产生新的值
fun CoroutineScope.square(nums: ReceiveChannel<Int>) = produce {
for (x in nums) send(x * x)
}
fun main() = runBlocking {
val job = launch {
val nums = produceNumbers()
val squares = square(nums)
repeat(5) {
printMsg("${squares.receive()}")
}
}
job.join()
printMsg("Done")
}
这个channel程序又没能执行完,很危险的,实际开发过程中,要避免。怎么避免,如果channel不好关闭,就cancel当前channel所在的协程(coroutineContext[Job]?.cancel())。
fun main() = runBlocking {
val job = launch {
val nums = produceNumbers()
val squares = square(nums)
repeat(5) {
printMsg("${squares.receiveCatching().getOrNull()}")
}
coroutineContext[Job]?.cancel() //取消当前所在的协程,可以终止程序!!!!!!
}
job.join()
printMsg("Done")
}
五、一个协程发射,多个协程接收,是线程安全的。
//不断的发射数据
fun CoroutineScope.produceNumbers(): ReceiveChannel<Int> = produce {
var x = 1
while (true) {
send(x++)
delay(100)
}
}
fun CoroutineScope.launchProcessor(id: Int, nums: ReceiveChannel<Int>) = launch {
for (msg in nums) {
printMsg("processorId $id ${msg}")
}
}
fun main() = runBlocking {
val nums = produceNumbers() //当前协程
repeat(5) { //开启5个子协程消费
launchProcessor(it, nums)
}
delay(950)
nums.cancel()
}
六、多个协程发射,一个协程接收。
suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
while (true) {
delay(time)
channel.send(s)
}
}
fun main() = runBlocking {
launch {
val channel = Channel<String>()
launch { sendString(channel, "foo", 200L) }
launch { sendString(channel, "Bar", 500L) }
repeat(6) {
printMsg("${channel.receive()}")
}
coroutineContext[Job]?.cancel() //及时终止channel,通过取消当前协程
}.join()
}