Kotlin协程Channel源码示例浅析
作者:wayne214 发布时间:2023-06-14 22:54:08
结论先行
Kotlin协程中的Channel用于处理多个数据组合的流,随用随取,时刻准备着,就像自来水一样,打开开关就有水了。
Channel使用示例
fun main() = runBlocking {
logX("开始")
val channel = Channel<Int> { }
launch {
(1..3).forEach{
channel.send(it)
logX("发送数据: $it")
}
// 关闭channel, 节省资源
channel.close()
}
launch {
for (i in channel){
logX("接收数据: $i")
}
}
logX("结束")
}
示例代码 使用Channel创建了一组int类型的数据流,通过send发送数据,并通过for循环取出channel中的数据,最后channel是一种协程资源,使用结束后应该及时调用close方法关闭,以免浪费不必要的资源。
Channel的源码
public fun <E> Channel(
capacity: Int = RENDEZVOUS,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
onUndeliveredElement: ((E) -> Unit)? = null
): Channel<E> =
when (capacity) {
RENDEZVOUS -> {}
CONFLATED -> {}
UNLIMITED -> {}
else -> {}
}
可以看到Channel的构造函数包含了三个参数,分别是capacity、onBufferOverflow、onUndeliveredElement.
首先看capacity,这个参数代表了管道的容量,默认参数是RENDEZVOUS,取值是0,还有其他一些值:
UNLIMITED: Int = Int.MAX_VALUE,没有限量
CONFLATED: 容量为1,新的覆盖旧的值
BUFFERED: 添加缓冲容量,默认值是64,可以通过修改VM参数:kotlinx.coroutines.channels.defaultBuffer,进行修改
接下来看onBufferOverflow, 顾名思义就是管道容量满了,怎么办?默认是挂起,也就是suspend,一共有三种分别是: SUSPNED、DROP_OLDEST以及DROP_LATEST
public enum class BufferOverflow {
/**
* Suspend on buffer overflow.
*/
SUSPEND,
/**
* Drop **the oldest** value in the buffer on overflow, add the new value to the buffer, do not suspend.
*/
DROP_OLDEST,
/**
* Drop **the latest** value that is being added to the buffer right now on buffer overflow
* (so that buffer contents stay the same), do not suspend.
*/
DROP_LATEST
}
SUSPEND,当管道的容量满了以后,如果发送方还要继续发送,我们就会挂起当前的 send() 方法。由于它是一个挂起函数,所以我们可以以非阻塞的方式,将发送方的执行流程挂起,等管道中有了空闲位置以后再恢复,有点像生产者-消费者模型
DROP_OLDEST,顾名思义,就是丢弃最旧的那条数据,然后发送新的数据,有点像LRU算法。
DROP_LATEST,丢弃最新的那条数据。这里要注意,这个动作的含义是丢弃当前正准备发送的那条数据,而管道中的内容将维持不变。
最后一个参数是onUndeliveredElement,从名字看像是没有投递成功的回调,也确实如此,当管道中某些数据没有成功接收时,这个就会被调用。
综合这个参数使用一下
fun main() = runBlocking {
println("开始")
val channel = Channel<Int>(capacity = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST) {
println("onUndeliveredElement = $it")
}
launch {
(1..3).forEach{
channel.send(it)
println("发送数据: $it")
}
// 关闭channel, 节省资源
channel.close()
}
launch {
for (i in channel){
println("接收数据: $i")
}
}
println("结束")
}
输出结果如下:
开始
结束
发送数据: 1
发送数据: 2
发送数据: 3
接收数据: 2
接收数据: 3
安全的从Channel中取数据
先看一个例子
val channel: ReceiveChannel<Int> = produce {
(1..100).forEach{
send(it)
println("发送: $it")
}
}
while (!channel.isClosedForReceive){
val i = channel.receive();
println("接收: $i")
}
输出报错信息:
Exception in thread "main" kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed
可以看到使用isClosedForReceive判断是否关闭再使用receive方法接收数据,依然会报错,所以不推荐使用这种方式。
推荐使用上面for循环的方式取数据,还有kotlin推荐的consumeEach方式,看一下示例代码
val channel: ReceiveChannel<Int> = produce {
(1..100).forEach{
send(it)
println("发送: $it")
}
}
channel.consumeEach {
println("接收:$it")
}
所以,当我们想要获取Channel当中的数据时,我们尽量使用 for 循环,或者是channel.consumeEach {},不要直接调用channel.receive()。
“热的数据流”从何而来?
先看一下代码
println("开始")
val channel = Channel<Int>(capacity = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) {
println("onUndeliveredElement = $it")
}
launch {
(1..3).forEach{
channel.send(it)
println("发送数据: $it")
}
}
println("结束")
}
输出:
开始
结束
发送数据: 1
发送数据: 2
发送数据: 3
可以看到上述代码中并没有 取channel中的数据,但是发送的代码正常执行了,这种“不管有没有接收方,发送方都会工作”的模式,就是我们将其认定为“热”的原因。
举个例子,就像去海底捞吃火锅一样,你不需要主动要求服务员加水,服务员看到你的杯子中水少了,会自动给你添加,你只管拿起水杯喝水就行了。
总的来说,不管接收方是否存在,Channel 的发送方一定会工作。
Channel能力的来源
通过源码可以看到Channel只是一个接口,它的能力来源于SendChannel和ReceiveChannel,一个发送管道,一个接收管道,相当于做了一个组合。
这也是一种良好的设计思想,“对读取开放,对写入封闭”的开闭原则。
来源:https://juejin.cn/post/7169078590640750599


猜你喜欢
- 现在的项目越来越多的都是打包成jar运行尤其是springboot项目,这时候配置文件如果一直放在项目中,每次进行简单的修改时总会有些不方便
- 1、接口:接口与抽象类一样,也是表示某种规则,一旦使用了该规则,就必须实现相关的方法。对于C#语言而言,由于只能继承自一个父类,因此若有多个
- 多数据源配置首先是配置文件这里采用yml配置文件,其他类型配置文件同理我配置了两个数据源,一个名字叫ds1数据源,一个名字叫ds2数据源,如
- 本文实例为大家分享了Unity3d实现跑马灯广播效果的具体代码,供大家参考,具体内容如下废话不多说,直接上代码using DG.Tweeni
- 目录问题为每个request设置超时值Http Handler给Request加上超时处理抛出正确的异常使用Handler总结HttpCli
- 大部分Java开发者都在使用Map,特别是HashMap。HashMap是一种简单但强大的方式去存储和获取数据。但有多少开发者知道HashM
- 在分布式系统中,我们会需要 ID 生成器的组件,这个组件可以实现帮助我们生成顺序的或者带业务含义的 ID。目前有很多经典的 ID 生成方式,
- 一 前言最近网上比较火的代码生成器,知识追寻者抽空试试了一下,感觉不是友好,只能说功能比较呆板吧,还需要自己玩填空题,修修补补,然后再次打开
- # 前言之前在学习C语言的时候,做过一个三子棋的小游戏,最近开始学习Java,就想着能不能用Java再把之前的练习重新实现一边,既然有这个想
- 在很多的Android项目中都需要用户登录、注册。这样的话在开发中做好保护用户密码的工作就显得尤为重要。这里我把自己的密码保护方法记录下来。
- 1. 是否需要整合 ?不需要 : 单独使用Springmvc. 需要将原先Spring中的内容通通迁移到Springmvc中. 例如:数据源
- java 抛出异常处理的方法为了避免调用的人不知道有异常,才抛出异常的,所以是谁掉用的久在哪里处理。说的对吗对.1、throws关键字通常被
- 有时候,我们需要将控件的背景颜色设定为透明,比如说label(标签)控件。那么,如何将控件的背景颜色设定为透明?是不是只要将控件的BackC
- 首先 下载 jedis.jar包然后再 工程设置里面找到Libraries,点击+。添加下载好的jedis.jar包。点击OK退出即可创建J
- mybatis-plus Condition拼接Sql语句各方法1.setSqlSelect—用于添加查询的列信息public Wrappe
- 安卓的三种本地的典型数据存储方式SharedPreferences以文件格式保存在本地存储中SQL数据库IDE : Android Stud
- 在Android控件View的文字周围添加图标,供大家参考,具体内容如下在控件TextView文字周围放置图片(基于TextView的But
- [LeetCode] 5. Longest Palindromic Substring 最长回文子串Given a string
- 一、过滤器(filter)过滤器处于客户端与Web资源(Servlet、JSP、HTML)之间,客户端与Web资源之间的请求和响应都要通过过
- 目录说明使用常见问题No such instance field: 'logger2'说明logback作为log4j的替代