如何拆分一个';热的';来自Kotlin中回调的事件流?
我正在处理一系列事件,通过回调到达下游“我想把它分成多个流,并对它们进行处理。所有事件都是从一个线程按顺序到达的(我不控制它,所以我不认为我可以在这里使用协同例程) 这里使用的正确结构是什么 我可以使用callbackFlow和sendBlocking很容易地创建一个流,但是语义似乎不一致,因为流并不冷。 将流拆分为多个下游流的最佳方式是什么(取决于事件的内容)。还是应该使用频道?它与我的源代码的“热度”相匹配,但整个下游轮询似乎都关闭了(在这种基本上是同步的情况下),而且许多方法似乎不赞成使用流 我可以通过使用“一路回调”来实现这一切,但这会产生比我希望的更紧密的耦合。有什么想法吗 编辑: 我的结局是这样的,似乎很有效:如何拆分一个';热的';来自Kotlin中回调的事件流?,kotlin,kotlin-coroutines,kotlin-coroutines-flow,Kotlin,Kotlin Coroutines,Kotlin Coroutines Flow,我正在处理一系列事件,通过回调到达下游“我想把它分成多个流,并对它们进行处理。所有事件都是从一个线程按顺序到达的(我不控制它,所以我不认为我可以在这里使用协同例程) 这里使用的正确结构是什么 我可以使用callbackFlow和sendBlocking很容易地创建一个流,但是语义似乎不一致,因为流并不冷。 将流拆分为多个下游流的最佳方式是什么(取决于事件的内容)。还是应该使用频道?它与我的源代码的“热度”相匹配,但整个下游轮询似乎都关闭了(在这种基本上是同步的情况下),而且许多方法似乎不赞成使用
fun testFlow(){
运行阻塞{
val原始=流量(“aap”、“noot”、“mies”、“wim”、“zus”、“jet”、“weide”、“does”)
val broadcast=original.broadcastIn(本)
val flow1=broadcast.openSubscription().receiveAsFlow().filter{it.length==4}
val flow2=broadcast.openSubscription().receiveAsFlow().filter{it.length==3}
flow1.collect{it->println(“四个字母:${it}”)}
flow2.collect{it->println(“三个字母:${it}”)}
}
}
简短回答
这个用例很快就会有一个热点,但在此期间,您可以在封面下使用广播频道
您可以从创建基于回调的API的冷流开始(参见Roman Elizarov的)。
然后使用以下方法使其变热并共享:
val original: Flow<String> = TODO("get original flow")
// create an implicit hot BroadcastChannel, shared between collectors
val sharedFlow = original.broadcastIn(scope).asFlow()
// create derived cold flows, which will subscribe (on collect) to the
// same hot source (BroadcastChannel)
val flow1 = sharedFlow.filter { it.length == 4 }
val flow2 = sharedFlow.filter { it.length == 3 }.map { it.toUppercase() }
flow1.collect { it -> println("Four letter: ${it}") }
flow2.collect { it -> println("Three letter: ${it}") }
val-original:Flow=TODO(“获取原始流”)
//创建一个在收集器之间共享的隐式热广播频道
val sharedFlow=original.broadcastIn(作用域).asFlow()
//创建派生的冷流,该冷流将订阅(收集时)到
//同一热源(广播频道)
val flow1=sharedFlow.filter{it.length==4}
val flow2=sharedFlow.filter{it.length==3}.map{it.toUppercase()}
flow1.collect{it->println(“四个字母:${it}”)}
flow2.collect{it->println(“三个字母:${it}”)}
使流变热(当前方式)
首先要澄清的是,即使目前Flow
s基本上是冷的,但是已经有一个热的StateFlow
,并且很快就会有一个方便和热的方法来简化这种用例
在我们等待的过程中,如果您最初有一个冷流
,那么您当前必须首先创建一个热通道(以及一个向其发送元素的协程),我们从中派生共享热源的流。这可以通过以下方式之一轻松完成:
- 在给定范围内启动协同程序,并为您提供一个
ReceiveChannel
(用于扇出,请参见下文)
- 在给定范围内启动协同程序,并为您提供
广播频道
(对实际共享有用,请参见下文)
一旦有了热通道,您就可以将其转换为流并获得不同的行为:
- 从热源创建
流
,但它只能由单个收集器收集(否则抛出)
- 创建多采集器
流
,但它以扇出方式运行(源通道中的每个元素仅流向一个使用者)
- 创建一个多收集器
流
,其中每个收集器获取所有元素(有效共享)。调用collect
在BroadcastChannel
上创建新订阅,并正确处理取消
带有StateFlow的“最新状态”语义
这不是您的用例,但有时您可能不需要流中的所有值,而是最新的当前状态和状态更新
这过去是通过合并的DBRoadCastChannel
完成的,但现在可以使用来表示这一点(自Corroutines 1.3.6以来):
- 在producer端,设置
MutableStateFlow
- 在使用者端,每个采集器在启动时将获得当前状态,然后每次与前一个不同时(基于
equal
ity)都将获得新的状态值
我已经拼凑了一个例子,我觉得我很好,谢谢你。@FrankLee我刚才描述的和你现在做的有些相似。我只是使用asFlow()
删除显式频道订阅,因为它会自动处理底层频道取消。请参阅我根据您的代码片段提供的示例代码。在这种情况下,使用receiveAsFlow()
不会自动取消订阅频道,您应该选择consumerasflow()
。也就是说,openSubscription().consumeAsFlow()
会立即创建两个热流,而使用asFlow()
实际上会使“子流”保持冷状态,并且只在订阅被收集时创建订阅。