消除异步属性刷新

时间:2019-01-10 16:36:46

标签: c# system.reactive throttling debouncing

感谢您在以下问题上的帮助: 我班上有一个属性,可以说

string Foo {get;set;}

该类中有一个刷新功能。有一个长期运行的方法可以更新

Foo = await Task.Run()... etc. 

当每秒调用1000 Refresh时,如何避免Task-s的堆积?防弹跳?节流吗?怎么做? Rx在项目中可用,我正在使用dotnet core 2.2。

类构造器


    res = Observable.FromAsync(() => Task.Run(async () =>
    {
                       await Task.Delay(5000);
                       return "Example";
    }
    )).Throttle(TimeSpan.FromSeconds(10));

课程


    private IObservable<string> res;

    public string Foo
    {
                get => _foo;
                set
                {
                    this.RaiseAndSetIfChanged(ref _foo, value);
                }
    }

    public void RefreshFoo()
    {
                res.Subscribe(x => Foo = x);
    }

2 个答案:

答案 0 :(得分:0)

最后我得到了这个解决方案。

工作原理如下:

  • 被呼叫后立即开始
  • 更多呼叫会被去抖,但会在刷新完成后再次运行
  • LongRunning工作在后台线程上,并且在每个项目上运行。
  • 同时刷新两个项目以避免ThreadPool饥饿

代码示例:

private long _refreshCalls;

public async Task RefreshHistoriesAsync()
{
    try
    {
        if (Interlocked.Read(ref _refreshCalls) == 2) //it is running and scheduled to rerun after finished
        {
            return;
        }
        if (Interlocked.Read(ref _refreshCalls) == 1) //it is running but now we will rerun if finished
        {
            Interlocked.Increment(ref _refreshCoinCalls);
            return;
        }
        if (Interlocked.Read(ref _refreshCalls) == 0) //it is not running so we start the work
        {
            Interlocked.Increment(ref _refreshCalls);
        }
        const int simultaneousThread = 2; //threads allowed to run simultaneously in threadpool

        await Task.Run(() => Parallel.ForEach(myListOfItemsToWorkOn, new ParallelOptions { MaxDegreeOfParallelism = simultaneousThread }, item =>
                {
                    LongRunningWork();
                }));

        if (Interlocked.Read(ref _refreshCoinCalls) == 2) //scheduled to rerun so we start the work again
        {
            Interlocked.Exchange(ref _refreshCoinCalls, 0);
            RefreshHistoriesAsync();
        }
        if (Interlocked.Read(ref _refreshCoinCalls) == 1) //done with the job
        {
            Interlocked.Exchange(ref _refreshCoinCalls, 0);
        }
    }
    catch (Exception ex)
    {
        Interlocked.Exchange(ref _refreshCoinCalls, 0);
        //Log error
    }
}

答案 1 :(得分:0)

如果您可以使用其他软件包,我建议使用ReactiveUI,它是ReactiveCommand,它将立即解决您的所有问题:

  var command = ReactiveCommand.CreateFromTask(async () =>
            { // define the work
                Console.WriteLine("Executing at " + DateTime.Now);
                await Task.Delay(1000);
                return "test";
            });

            command.Subscribe(res => {
                // do something with the result: assign to property
            });

            var d = Observable.Interval(TimeSpan.FromMilliseconds(500), RxApp.TaskpoolScheduler) // you can specify scheduler if you want
                .Do(_ => Console.WriteLine("Invoking at " + DateTime.Now))
                .Select(x => Unit.Default) // command argument type is Unit
                .InvokeCommand(command); // this checks command.CanExecute which is false while it is executing

输出:

Invoking at 2019-01-22 13:34:04
Executing at 2019-01-22 13:34:04
Invoking at 2019-01-22 13:34:05
Invoking at 2019-01-22 13:34:05
Executing at 2019-01-22 13:34:05

我知道该软件包主要用于UI开发,但没有什么好用的技巧,例如ReactiveCommand,您可以在任何地方使用它。

请注意,await command.Execute()在默认情况下不会检查是否可以执行命令。

我认为这比您的解决方案更具可读性。