Hi. We know that `channelFlow` can do what `flow` ...
# coroutines
e
Hi. We know that
channelFlow
can do what
flow
can't e.g. • Retrieve value from a concurrently run coroutineCan use a non-suspending way i.e. `trySend` to send data It looks like it is more powerful than
flow
. I wonder • if there's anything where
flow
can do but not in
channelFlow
, or • if there's anything
flow
is preferred over
channelFlow
(e.g. in terms of efficiency or performance?)? The reason I ask is, I want to see if we can just use
channelFlow
for everything instead, or what's the scenario we should use
flow
instead? I may have missed out something fundamental?
j
I guess the main difference is that
channelFlow
uses (you guessed it) a channel under the hood. The elements you send are sent via a channel and then go to the flow collector. This means there is some extra synchronization (and that's what allows you to send from other coroutines). I would assume this is more complex under the hood and might be slightly slower than a simple
flow
(I haven't measured, though). Why would you want to use
channelFlow
for everything, though? If you just need a simple flow, why not use it? Note that there is also
callbackFlow
which is more appropriate when wrapping callback-based APIs that call their callbacks multiple times. It may warn you better if you're doing things wrong.
e
Thanks @Joffrey. fully grasp
callbackFlow
is actually
channelFlow
that force (remind) us to use
awaitClose
. That's all good. I'm just curious on
flow
vs the the
channelFlow
, as I cannot really think of what one
channelFlow
cannot do, other than suspect there's some complex performance behind the scene, as you mentioned. It's more of a curious learning than actually having explore any use case.
j
Yeah then as a simple answer I don't believe
flow
can do more things than
channelFlow
. But I would argue that, even if there were no performance difference, being able to do less is often a good thing 🙂 For instance
List
can do less than
MutableList
, and this is the main reason why I use
List
over
MutableList
whenever I can. We usually don't conclude that, since
MutableList
can do everything
List
can, we should just use
MutableList
everywhere.
n
When you introduce a channel, you lose the ability to know when the downstream is done processing an item. You only, at best with buffer size of 1, know when it has pulled the item from the channel, which only means it has started processing it.
Copy code
(1..10).asFlow()
            .onEach { println("onEach $it") } //Will print the 6
            .buffer(1)
            .take(5)
            .collect { println("collect $it") }
Buffers also further complicate your code since every point where you use a buffer introduces concurrency. The upstream is running in a separate coroutine than the downstream meaning upstream operators can handle newer items concurrently while downstream operators handle older items. This can be exactly what you want in some cases but it can also be especially dangerous if side effects are involved.
j
Technically you can use a rendez-vous channel (buffer size 0) and this will make the producer wait until the end of the previous item processing before sending the next. But I guess you're right anyway, there is still an overlap of one item 🤔 very good points you make here
e
Thanks!! This explains much!! Agree with all the points above 🙌 !!
A little more investigation found that
channelFlow
with
RENDEZVOUS
is not the same as
Flow
. Explanation here. @Nick Allen is correct stating that the default
Flow
behaviour where each emitting has to be wait for it to be consumed is unique to the
Flow
itself.