Kotlin协程开发之Flow的融合与Channel容量及溢出策略介绍

作者:LeeDuo. 时间:2023-07-02 23:52:01 

一.协程间的通信

当需要进行协程间的通信时,可以调用Channel方法,创建一个Channel接口指向的对象,通过调用该对象的send方法和receive方法实现消息的发送与接收。协程对Channel接口的实现,本质上与阻塞队列类似,这里不再赘述。

1.通道容量

事实上,send方法与receive方法并没有定义在Channel接口中,而是分别定义在SendChannel接口和ReceiveChannel接口中。Channel接口中只是定义了一些与Channel容量策略相关的枚举常量,代码如下:

// 继承SendChannel接口和ReceiveChannel接口
public interface Channel<E> : SendChannel<E>, ReceiveChannel<E> {
   // 枚举常量
   public companion object Factory {
       // Channel的容量为无限
       public const val UNLIMITED: Int = Int.MAX_VALUE
       // Channel的容量为0,没有缓存
       public const val RENDEZVOUS: Int = 0
       // Channel的容量为1,溢出策略为DROP_OLDEST,
       // 后一个的数据会覆盖前一个数据
       public const val CONFLATED: Int = -1
       // Channel的容量为默认值CHANNEL_DEFAULT_CAPACITY,
       // 默认溢出策略为SUSPEND,send方法会发生挂起
       // 当容量策略为BUFFERED,而溢出策略不为SUSPEND时,Channel的容量为1
       public const val BUFFERED: Int = -2
       // 协程内部使用的一个默认枚举值,不对外暴露
       internal const val OPTIONAL_CHANNEL = -3
       // 用于手动配置容量策略为BUFFERED时的默认值
       public const val DEFAULT_BUFFER_PROPERTY_NAME: String = "kotlinx.coroutines.channels.defaultBuffer"
       // 容量策略为BUFFERED时的默认值
       // 默认64,最小1,最大为Int.MAX_VALUE-1
       internal val CHANNEL_DEFAULT_CAPACITY = systemProp(DEFAULT_BUFFER_PROPERTY_NAME,
           64, 1, UNLIMITED - 1
       )
   }
}

从上面的代码可以看出Channel接口继承自SendChannel接口和ReceiveChannel接口。因此,一个Channel接口指向的对象,既可以用于发送消息,也可以用于接收消息。

2.溢出策略

Channel除了容量策略外,还有溢出策略,用于决定当Channel的容量已满时,而下一个消息到来时的行为。溢出策略定义在枚举类BufferOverflow中,代码如下:

public enum class BufferOverflow {
   // 当容量已满时,挂起调用send方法的协程
   SUSPEND,
   // 当容量已满时,删除旧数据,将新的数据添加进去,不挂起调用send方法的协程
   DROP_OLDEST,
   // 当容量已满时,忽略当前要添加的数据,不挂起调用send方法的协程
   DROP_LATEST
}

二.FusibleFlow接口

FusibleFlow接口继承自Flow接口。一个类实现了该接口,表示该类创建的流可以与其上游或下游相邻的流进行融合,当流发生融合时,就会调用接口中定义的fuse方法,代码如下:

@InternalCoroutinesApi
public interface FusibleFlow<T> : Flow<T> {
   // 用于流的融合
   public fun fuse(
       context: CoroutineContext = EmptyCoroutineContext,
       capacity: Int = Channel.OPTIONAL_CHANNEL,
       onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
   ): Flow<T>
}

FusibleFlow接口的fuse方法,默认容量为OPTIONAL_CHANNEL,默认溢出策略为SUSPEND。

流的融合

在Flow中,当channelFlow方法、flowOn方法、buffer方法、produceIn方法、broadcastIn方法相邻调用时,就会触发流的融合。

具体融合的过程,其实是将下游流的容量、溢出策略、上下文传递给上游的流处理,上游的流根据自身的容量、溢出策略、上下文以及下游的流的容量、溢出策略、上下文重新计算,得到新的容量、溢出策略、上下文,并返回一个融合后的流。

三.ChannelFlow类

ChannelFlow类是一个抽象类,实现了FusibleFlow接口。下面分析一下fuse方法对于上下游流融合的策略,代码如下:

@InternalCoroutinesApi
public abstract class ChannelFlow<T>(
   // 上游流的上下文
   @JvmField public val context: CoroutineContext,
   // 上下游之间流的缓存容量
   @JvmField public val capacity: Int,
   // 溢出策略
   @JvmField public val onBufferOverflow: BufferOverflow
) : FusibleFlow<T> {
   ...
   public override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow): Flow<T> {
       // CONFLATED是一个复合的类型,需要拆解成capacity = 0, onBufferOverflow = DROP_OLDEST
       assert { capacity != Channel.CONFLATED }
       // 计算融合后流的上下文
       val newContext = context + this.context
       // 用于保存融合后流的容量
       val newCapacity: Int
       // 用于保存融合后流的溢出策略
       val newOverflow: BufferOverflow
       // SUSPEND为默认溢出策略,如果溢出策略不是默认的策略
       if (onBufferOverflow != BufferOverflow.SUSPEND) {
           // 直接保存
           newCapacity = capacity
           newOverflow = onBufferOverflow
       } else { // 如果是默认策略
           // 计算并保存新的容量
           newCapacity = when {
               // 如果之前的容量为默认枚举值,则使用新的
               this.capacity == Channel.OPTIONAL_CHANNEL -> capacity
               // 如果新的容量为默认枚举值,则使用原来的
               capacity == Channel.OPTIONAL_CHANNEL -> this.capacity
               // 如果原来的容量为默认值CHANNEL_DEFAULT_CAPACITY,则使用新的
               this.capacity == Channel.BUFFERED -> capacity
               // 如果新的容量为默认值CHANNEL_DEFAULT_CAPACITY,则使用原来的
               capacity == Channel.BUFFERED -> this.capacity
               // 如果不为默认值或默认枚举值
               else -> {
                   // 检查容量都是大于等于0的
                   assert { this.capacity >= 0 }
                   assert { capacity >= 0 }
                   // 将原来的容量和新的容量进行相加
                   val sum = this.capacity + capacity
                   // 如果相加后大与等于0,则容量为相加后的结果,否则为无限
                   if (sum >= 0) sum else Channel.UNLIMITED
               }
           }
           // 保存溢出策略
           newOverflow = this.onBufferOverflow
       }
       // 如果融合的两个流的上下文相同,容量相同,溢出策略也相同
       if (newContext == this.context && newCapacity == this.capacity && newOverflow == this.onBufferOverflow)
           // 则直接返回
           return this
       // 有变化则根据新计算出得参数,创建融合后的流
       return create(newContext, newCapacity, newOverflow)
   }
   // 由子类进行重写
   protected abstract fun create(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow): ChannelFlow<T>
   ...
}

流融合的原则

根据上面对fuse方法的分析,可以总结出fuse方法在计算容量和溢出策略时的四个原则:

1)下游优先于上游

2)溢出策略优先于容量

3)非默认值优先于默认值

4)上下游容量都不为默认值,则相加取和

来源:https://blog.csdn.net/LeeDuoZuiShuai/article/details/126617494

标签:Kotlin,Flow,融合,Channel,容量,溢出策略
0
投稿

猜你喜欢

  • SpringBoot导入Druid运行失败问题

    2023-02-14 03:35:41
  • Spring零基础入门WebFlux响应式编程

    2022-07-12 07:25:53
  • IDEA的Swing可视化插件JFormDesigner详解

    2023-09-23 08:02:54
  • 关于Java从本地文件复制到网络文件上传

    2022-05-10 16:49:02
  • 浅谈android @id和@+id的区别

    2021-10-28 06:06:09
  • C# [ImportDll()] 知识小结

    2022-09-15 07:42:18
  • MyBatis-Plus通过version机制实现乐观锁的思路

    2023-09-16 06:58:27
  • Spring整合WebSocket应用示例(上)

    2023-05-05 10:09:21
  • Flutter模仿实现微信底部导航栏流程详解

    2023-06-21 11:46:12
  • 简单了解Java中的可重入锁

    2023-12-18 12:29:19
  • C# winform实现右下角弹出窗口结果的方法

    2023-02-05 14:22:57
  • java开发BeanUtils类解决实体对象间赋值

    2022-08-25 06:31:25
  • Android获取apk签名指纹的md5值(防止重新被打包)的实现方法

    2023-06-23 04:26:12
  • 简单说说Java SE、Java EE、Java ME三者之间的区别

    2022-01-20 06:05:10
  • Netty分布式高性能工具类异线程下回收对象解析

    2023-07-09 21:02:43
  • C++string中的insert()插入函数详解

    2023-11-02 14:05:51
  • 猜数游戏java实现代码

    2023-01-05 18:39:28
  • HTTP中get和post的区别详解

    2023-04-19 11:42:18
  • springboot+spring data jpa实现新增及批量新增方式

    2023-01-27 03:45:53
  • Android中复制图片的实例代码

    2023-01-07 21:34:26
  • asp之家 软件编程 m.aspxhome.com