简体   繁体   English

您如何从 Kotlin 中的回调中拆分“热门”事件 stream?

[英]How do you split a 'hot' stream of events from a callback in Kotlin?

I'm processing a hot stream of events, arriving by callback.我正在处理一个热的 stream 事件,通过回调到达。 'Downstream' I'd like to split it into multiple streams, and process them.The events all arrive sequentially from a single thread (which I don't control, so I don't think I can use co routines here) What is the right structure to use here? '下游'我想把它分成多个流,并处理它们。事件都是从一个线程顺序到达的(我不控制它,所以我认为我不能在这里使用协程) 什么是在这里使用正确的结构?

I can create a Flow pretty easily, using callbackFlow and sendBlocking, but the semantics don't seem to line up, as the Flow isn't cold.我可以使用 callbackFlow 和 sendBlocking 非常轻松地创建流,但语义似乎不一致,因为流并不冷。 What is the best way to split a flow into multiple downstream flows (depending on the contents of events).将流拆分为多个下游流的最佳方法是什么(取决于事件的内容)。 Or should I use channels?还是我应该使用频道? It matches the 'hotness' of my source, but the whole polling downstream seems off (in this basically synchronoussituation), and a lot of the methods seem deprecated in favor of Flow.它与我的来源的“热度”相匹配,但整个下游轮询似乎关闭(在这种基本上同步的情况下),并且许多方法似乎已被弃用以支持 Flow。

I can do all this by just using 'callbacks all the way' but that creates a lot tighter coupling than I'd like.可以通过只使用“一路回调”来完成所有这些,但这会产生比我想要的更紧密的耦合。 Any ideas?有任何想法吗?

Edit:编辑:

I ended up with this, seems to work:我结束了这个,似乎工作:

    fun testFlow() {
        runBlocking {
            val original = flowOf("aap", "noot", "mies", "wim", "zus","jet","weide","does")
            val broadcast = original.broadcastIn(this)
            val flow1 = broadcast.openSubscription().receiveAsFlow().filter { it.length == 4 }
            val flow2 = broadcast.openSubscription().receiveAsFlow().filter { it.length == 3 }
            flow1.collect { it -> println("Four letter: ${it}") }
            flow2.collect { it -> println("Three letter: ${it}") }
        }
    }

Short answer简答

There will soon be a hot SharedFlow for this use case, but in the meantime you can use a BroadcastChannel under the cover.这个用例很快就会有一个热门的SharedFlow ,但与此同时,您可以在幕后使用BroadcastChannel

You can start with callbackFlow to create a cold flow from a callback based API (see Roman Elizarov's post about it ).您可以从 callbackFlow 开始,从基于 API 的callbackFlow创建冷流(请参阅 Roman Elizarov 的相关帖子)。 Then use the following to make it hot and share it:那就用下面的来火热分享一下吧:

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}") }

Making a flow hot (the current way)使流变热(当前方式)

First to clarify, even if Flow s are mostly cold for now, there is already a hot StateFlow , and there will soon be a convenient share operator and a hot SharedFlow to simplify this kind of use case.首先澄清一下,即使Flow现在大部分都是冷的,但已经有一个热的StateFlow ,并且很快就会有一个方便的share运算符和一个热的SharedFlow来简化这种用例。

While we wait for this, if you initially have a cold Flow , you currently have to first create a hot channel (and a coroutine to send elements to it) from which we derive flows sharing the hot source.当我们等待这个时,如果您最初有一个Flow ,您目前必须首先创建一个热通道(和一个协程来向它发送元素),我们从中派生共享热源的流。 This can easily be done in one of these ways:这可以通过以下方式之一轻松完成:

  • Flow.produceIn(scope) launches a coroutine in the given scope and gives you a ReceiveChannel (useful for fan-out, see below) Flow.produceIn(scope)在给定的 scope 中启动协程并为您提供ReceiveChannel (对于扇出很有用,请参见下文)
  • Flow.broadcastIn(scope) launches a coroutine in the given scope and gives you a BroadcastChannel (useful for actual sharing, see below) Flow.broadcastIn(scope)在给定的 scope 中启动协程并为您提供BroadcastChannel (对于实际共享很有用,请参见下文)

Once you have a hot channel, you can convert it into a flow and get different behaviours:拥有热通道后,您可以将其转换为流并获得不同的行为:

  • ReceiveChannel.consumeAsFlow() creates a Flow from a hot source, but it can only be collect -ed by a single collector (throws otherwise) ReceiveChannel.consumeAsFlow()从热源创建一个Flow ,但它只能由单个collect器收集(否则抛出)
  • ReceiveChannel.receiveAsFlow() creates a multi-collector Flow , but it behaves in a fan-out fashion (each element form the source channel only goes to one consumer) ReceiveChannel.receiveAsFlow()创建一个多收集器Flow ,但它以扇出方式运行(来自源通道的每个元素仅进入一个消费者)
  • BroadcastChannel.asFlow() creates a multi-collector Flow where each collector gets all elements (which is effectively sharing). BroadcastChannel.asFlow()创建一个多收集器Flow ,其中每个收集器获取所有元素(有效共享)。 Calling collect creates a new subscription on the BroadcastChannel , and handles cancellation properly.调用collectBroadcastChannel上创建一个新订阅,并正确处理取消。

"Latest state" semantics with StateFlow StateFlow的“最新状态”语义

This is not your use case, but sometimes you may not want necessarily all values in a flow, but rather the latest current state and state updates.这不是您的用例,但有时您可能不需要流程中的所有值,而是最新的当前 state 和 state 更新。

This used to be done via a ConflatedBroadcastChannel , but you can now use a StateFlow to represent this (since coroutines 1.3.6):这过去是通过ConflatedBroadcastChannel完成的,但您现在可以使用StateFlow来表示它(自协程 1.3.6 起):

  • on the producer side, set the value of a MutableStateFlow在生产者端,设置MutableStateFlow的值
  • on the consumer side, each collector will get the current state when they start, and then get a new state value each time it is different from the previous one (based on equal ity).在消费者端,每个收集器在启动时都会得到当前的state,然后每次与前一次不同时得到一个新的state值(基于equal性)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM