Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

combine(flow1, flow2) does not work instantly #2280

Closed
aartikov opened this issue Oct 5, 2020 · 9 comments
Closed

combine(flow1, flow2) does not work instantly #2280

aartikov opened this issue Oct 5, 2020 · 9 comments

Comments

@aartikov
Copy link

aartikov commented Oct 5, 2020

Hi.
I am writing a mini-framework with reactive properties. One of the core concepts in this framework is a computed property. This property recalculates its value automatically whenever values of some other properties are changed.

For example:

    var appleCount by state(0)
    var bananaCount by state(0)
    val fruitCount by computed(::appleCount, ::bananaCount) { a, b -> a + b }

    fun changeFruits() {
        appleCount = 2
        bananaCount = 3
        // fruitCount is 5 here
    }

My framework uses MutableStateFlow and combine under the hood.
The problem is that it is not always works as expected. The computed value is not updated instantly in some cases.
For example:

    fun changeFruits() {
        appleCount = 2
        bananaCount = 3
        appleCount = 20
        bananaCount = 30
        // fruitCount is 5 here, but 50 is expected
    }

I wrote a test to reproduce the problem:

    @Test
    fun `flows are combined instantly`() = runBlockingTest {
        val flow1 = MutableStateFlow(0)
        val flow2 = MutableStateFlow(0)
        val resultFlow = MutableStateFlow(0)

        val job = launch {
            combine(flow1, flow2) { v1, v2 ->
                v1 + v2
            }.collect {
                resultFlow.value = it
            }
        }

        flow1.value = 2
        flow2.value = 3
        flow1.value = 20
        flow2.value = 30

        assertEquals(50, resultFlow.value)
        job.cancel()
    }

In the real code I use Main.immediate dispatcher. That's why I expect an instant recalculation.

Take a look, please.
If it is an expected behavior, how can I solve my problem?

@aartikov aartikov changed the title combile(flow1, flow2) does not work instantly combine(flow1, flow2) does not work instantly Oct 5, 2020
@elizarov
Copy link
Contributor

elizarov commented Oct 5, 2020

It is expected behavior. Coroutines are asynchronous. The "immediate" dispatcher is a bit of performance-optimization trick, not a guarantee of any kind of synchrony in execution and it still, in fact, uses an event queue under the hood, that can get coroutines execution deferred. If you are looking to get "instantly computed" properties, you should use some other framework or create your own framework. Flow is inherently designed to be asynchronous, as opposed to being synchronous.

@psteiger
Copy link

psteiger commented Oct 6, 2020

The "immediate" dispatcher is a bit of performance-optimization trick, not a guarantee of any kind of synchrony in execution and it still, in fact, uses an event queue under the hood, that can get coroutines execution deferred.

Is the same true for Dispatchers.Unconfined and CoroutineStart.UNDISPATCHED? Can I not expect synchronous execution from those?

@mhernand40
Copy link

This looks somewhat similar to #2082.

@elizarov
Copy link
Contributor

elizarov commented Oct 6, 2020

You cannot expect synchronous execution with kotlinx.coroutines library. The underlying language mechanics for Kotlin coroutines can be used to implement synchronous coroutines, though (for example see a sequence { ... } builder in the standard library that is fully synchronous), but the whole kotlinx.coroutines library, including Kotlin Flow abstraction, is centered around asynchronous coroutines.

@aartikov
Copy link
Author

aartikov commented Oct 6, 2020

@elizarov
What about single MutableStateFlow? There are guaranties that the values will be collected instantly when Main.immediate dispatcher is used?
For example:

    @Test
    fun `state flow is collected instantly`() = runBlockingTest {
        val flow = MutableStateFlow("")
        var result = ""

        val job = launch {
            flow.collect {
                result += it
            }
        }

        result += "1"
        flow.value = "A"
        result += "2"
        flow.value = "B"
        result += "3"
        flow.value = "C"

        assertEquals("1A2B3C", result)
        job.cancel()
    }

@elizarov
Copy link
Contributor

elizarov commented Oct 6, 2020

There is no such guarantee. In simple cases it will, indeed, work seemingly synchronously. In complex nested cases, when you mutate a value inside another mutation (which has been already immedaitely-dispatched) it will not work this way.

@aartikov
Copy link
Author

aartikov commented Oct 7, 2020

@elizarov
StateFlow looks like a good building block to represent state of ViewModel in Android.
But I worry that asynchronous nature of flows is a source of pitfalls here. Empty or irrelevant data can flicker for a short time if a view is not updated as soon as possible. I have experienced such problems when used BehaviorSubject with observeOn(AndroidSchedulers.mainThread()) from RxJava. However my experiments with StateFlow show that it works fine in most cases.
Could you please clarify it?

@elizarov
Copy link
Contributor

elizarov commented Oct 7, 2020

There is a guarantee that it updates as soon as possible. Whenever possible updates are synchronous. But this happens not because it is designed to provide synchronous updates, it happens because it is designed to provide updates as fast as possible which is not the same as you'd find in synchronous data-binding frameworks. As long as you use it for a direct viewmodel -> view communication, though, it will happen fast and synchronously.

@aartikov
Copy link
Author

aartikov commented Oct 7, 2020

Thanks 👍

@qwwdfsad qwwdfsad closed this as completed Oct 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants