Odonno / ReduxSimple

Simple Stupid Redux Store using Reactive Extensions
http://www.nuget.org/packages/ReduxSimple/
MIT License
143 stars 19 forks source link

How can I wait for all the asynchronous task effects completed when Dispath an action ? #86

Closed RockNHawk closed 3 years ago

RockNHawk commented 3 years ago

Hi, I have tested that if the effect is running in an asynchronous task, the store.Dispatch method will not wait for the effect to complete.

Sometimes I need to wait for all the asynchronous task effects completed, how can I do this ?

Thank you!

using Shouldly;
using Xunit;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Converto;
using System.Reactive.Linq;
using static ReduxSimple.Tests.Setup.TodoListStore.Functions;
using static ReduxSimple.Reducers;
using static ReduxSimple.Effects;

namespace ReduxSimple.Tests.Async
{
    using AsyncStore = ReduxSimple.ReduxStore<AsyncState>;
    public class AsyncState
    {
        public int NumberVal1 { get; set; }
        public int EffectNumberVal { get; set; }
    }

    public class AddNumAction
    {
        public int Val { get; set; }
    }

    public class EffectAddNumAction
    {
        public int Val { get; set; }
    }

    public class AsyncTests
    {
        [Fact]
        public void CanDispatchAsyncWithEffects()
        {
            var initialState = new AsyncState() { };
            var store = new AsyncStore(
                CreateReducers(),
                initialState
            );

            int effectRunCount = 0;
            int effectTaskDoneCount = 0;

            var effectWithDispatch = CreateEffect<AsyncState>(
                () => store.ObserveAction<AddNumAction>()
                    .Do(_ => { Interlocked.Increment(ref effectRunCount); })
                    .Select(_ =>
                    {
                        return Observable.FromAsync(token => Task.Run(() =>
                        {
                            Thread.Sleep(1000);
                            Interlocked.Increment(ref effectTaskDoneCount);
                            return new EffectAddNumAction {Val = 1};
                        }, token));
                    })
                    .Switch()
                ,
                true
            );

            store.RegisterEffects(
                effectWithDispatch
            );

            int loop = 10;

            for (int i = 0; i < loop; i++)
            {
                store.Dispatch(new AddNumAction() {Val = 1});
                store.State.NumberVal1.ShouldBe(i + 1);
                //store.State.EffectNumberVal.ShouldBe(i + 1);
            }

            store.State.NumberVal1.ShouldBe(loop);
            //store.State.EffectNumberVal.ShouldBe(loop);
        }

        public static IEnumerable<On<AsyncState>> CreateReducers()
        {
            return new List<On<AsyncState>>
            {
                On<AddNumAction, AsyncState>(HandleNum),
                On<EffectAddNumAction, AsyncState>(HandleEffectNum),
            };
        }

        private static AsyncState HandleNum(AsyncState state, AddNumAction action)
        {
            return state.With(
                new
                {
                    NumberVal1 = state.NumberVal1 + action.Val,
                }
            );
        }

        private static AsyncState HandleEffectNum(AsyncState state, EffectAddNumAction action)
        {
            return state.With(
                new
                {
                    EffectNumberVal = state.EffectNumberVal + action.Val,
                }
            );
        }

    }
}
Odonno commented 3 years ago

Hi @RockNHawk

What was the expected behavior? Dispatch function is sync but Effects are async. So Dispatch will run immediately while Effects can take more time and can come after all Dispatch.

RockNHawk commented 3 years ago

@Odonno Yeah, that's the correct behavior, and how could I await the action's effects done ? it seems currently not supported ? so I needs extend it / make a hack ?

Odonno commented 3 years ago

Well, I don't think I understand your problem. Are you able to provide a more concrete example?

RockNHawk commented 3 years ago

@Odonno I use ReduxSimple to update/record/display the status information of a process that continues for a long time.

I use Dispatch Action to trigger the Reducer to update the state. When a certain amount is processed, I want to persist the state to the database through Effects, and hope to wait for the Effects processing to complete before continuing the subsequent processing, but currently if I call store.Dispatch(XxxAction) Effects are processed asynchronously, and I want to wait for the effects to be processed before continuing.

Odonno commented 3 years ago

From what I understand, you want to mimic a Queue mechanism within the Effect. You could use a standard Queue but if you want to use Rx.NET, which would fit with ReduxSimple, I think you may need to use the Buffer operator: http://introtorx.com/Content/v1.0.10621.0/13_TimeShiftedSequences.html#Buffer

Does it help?

RockNHawk commented 3 years ago

Thank you for your suggestion. I already have a batch mechanism. I just want to wait for the async effect complete when a "BatchAction" dispatched. Maybe I have to perform persistence operations outside of ReduxSimple, and this problem will be resolved.