Android Kotlin协程间的通信Channel介绍

2022-05-25 09:07:56 浏览数 (1)

前言

使用Kotlin做Android项目时,肯定少不了使用协程,而在协程的使用中,少不了要在不同的协程中传递数据,而Kotlin中的Channel,就是专门用来处理协程之间的通信,今天这篇就是来看看Channel的用法。

Channel简介

channel用于协程间的通信, 允许我们在不同的协程间传递数据。channel非常类似于一个 java 中非常常见的概念BlockingQueue,元素从一端被加入, 从另一端被消费.。当需要的时候, 多个协程可以向同一个channel发送数据, 一个channel的数据也可以被多个协程接收。当多个协程从同一个channel接收数据的时候, 每个元素仅被其中一个consumer消费一次. 处理元素会自动将其从channel里删除。

Channel的用法

channel可以简单的通过send和receive实现。

代码语言:javascript复制
fun main() = runBlocking{
    val intchannel = Channel<Int>()
    launch {
        for(x in 1..10) intchannel.send(x)
    }

    repeat(10){ 
        val recv = intchannel.receive()
        println("read:$recv")
    }
}

输出结果

channel还可以通过遍历来实现receive。

代码语言:javascript复制
fun main() = runBlocking{
    val intchannel = Channel<Int>()
    launch {
        for(x in 1..6) intchannel.send(x)
    }

    for(recv in intchannel) println("read:$recv")
    println("receive finish")
}

输出结果

从上图中可以看到,通过for的方式可以直接赋值到recv里打印出来了,但是在代码的结尾中我们的println("receive finish"),并没有在控制台打印出来,程序也没有退出,这是因为接收者在协程中还一直在等待。

想要正常结束并退出,接下来就要用到channel的关闭了,Channel可以被关闭, 说明没有更多的元素了。取消协程也会关闭channel。那我们改一下上面的代码,加上close。

代码语言:javascript复制
fun main() = runBlocking{
    val intchannel = Channel<Int>()
    launch {
        for(x in 1..6) intchannel.send(x)
        intchannel.close()
    }

    for(recv in intchannel) println("read:$recv")
    println("receive finish")
}

代码中加入了close后,可以看到,channel接收完后,打印出的finish并且退出程序了。

channel的类型

Channel有四种不同的类型,类型间不同的区别有两点,一是定义内部可以存储的元素,二是Send方式是否可以被挂起;而所有channel类型的Receive方法都是同样的行为,如果channel不为空, 接收一个元素, 否则挂起,具体的类型如下:

  1. Rendezvous channel(默认类型): 0尺寸buffer,Send和Receive要见面,否则Send挂起,就是说每Send完的后,如果一直没有Receive,即被挂起。
  2. Buffered channel:指定元素大小,当满了后Send会被挂起。
  3. Conflated channel: 新元素会覆盖旧元素,receiver只会得到最新元素,Send永不挂起。
  4. Unlimited channel: 无限元素,Send不被挂起。

接下来我们做个用多个协程向同一个Channel发送数据的例子来看看这四种模式。

01Rendezvous channel(默认类型)

代码语言:javascript复制
fun main() {
    val channel = Channel<String>()
    runBlocking {
        val res1 = async {
            for (x in 1..5) {
                channel.send("I am $x")
            }
            return@async "res1 over"
        }
        val res2 = async {
            for (y in 11..15) {
                channel.send("you are $y")
            }
            return@async "res2 over"
        }
        val res3 = async {
            for (z in 21..25) {
                channel.send("he is $z")
            }
            return@async "res3 over"
        }
        launch {
            println(" async finish ${res1.await()   res2.await()   res3.await()} ")
            channel.close()
        }
        for (item in channel) {
            println(item)
        }

        println("finish")
    }
}

代码中用async开启三个协程,当三个协程执行完后,关闭channel。下面是输出结果。

02Buffered channel

代码语言:javascript复制
val channel = Channel<String>(3)

当channel中后面加上数字3,然后再运行看。

上图中,因为满了3个后Send挂起,所以第一个协程(1-5)完后,第二个协程的11数字进去后也开始挂起了,这时的挂起也让第三个协程(21-25)的第一条进入到队列中。

03Conflated channel

代码语言:javascript复制
val channel = Channel<String>(Channel.CONFLATED)

当channel中后面参数加上Channel.CONFLATED,然后再运行看。

可以看到输出的只有两条,第一条数据和最后一条数据。

04Unlimited channel

代码语言:javascript复制
val channel = Channel<String>(Channel.UNLIMITED)

当channel中后面参数加上Channel.UNLIMITED,然后再运行看。

这里就可以看出,当使用Channel.UNLIMITED时,完全是按钮协程调用的顺序输出的。

Kotlin使用协程时,还是会经常用Channel来处理协程之间的数据通信,更多的用法可以自己去多做尝试

0 人点赞