如何优化递归函数的反应式实现

本文关键字:递归函数 反应式 实现 优化 何优化 | 更新日期: 2023-09-27 18:24:09

我尝试使用Rx库在C#中创建一个具有动态间隔的自定义Reactive计时器。由于我在性能和行数方面都需要最优化的代码,所以我最终使用了递归方法。代码如下:

  public static IObservable<TOut> GenerateAsync<TResult, TOut>(
  Func<Task<TResult>> initialState,
  Func<TResult, bool> condition,
  Func<TResult, Task<TResult>> iterate,
  Func<TResult, TimeSpan> timeSelector,
  Func<TResult, TOut> resultSelector,
  IScheduler scheduler = null)
    {
        var s = scheduler ?? Scheduler.Default;
        return Observable.Create<TOut>(async obs =>
        {
            //You have to do your initial time delay here.
            var init = await initialState();
            //Process the result
            obs.OnNext(resultSelector(init));
            return s.Schedule(init, timeSelector(init), async (state, recurse) =>
            {
                //Check if we are done
                if (!condition(state))
                {
                    obs.OnCompleted();
                    return;
                }
                //Initiate the next request
                state = await iterate(state);
                //Process the result
                obs.OnNext(resultSelector(state));
                //Recursively schedule again
                recurse(state, timeSelector(state));
            });
        });
    }

这种方法的问题在于,由于它是递归的,因此存在巨大的堆栈增长。我针对定时器的使用情况测试了这种方法,它的内存使用量几乎是原来的两倍。

用于测试代码的链接,我曾获得内存使用测试。

如何在不失去使用能力的情况下优化此功能?

如何优化递归函数的反应式实现

这看起来有点像任务和可观察对象的混合——仅可观察对象就足以表达。

要回答您的基本问题,需要创建一个具有可变时间段的间隔计时器,该计时器在计时器运行时提供。这可以表示为一个可观测值,它以TimeSpan的可观测值作为输入,并返回tick作为输出,就像内置的Observable.Interval一样。

    static IObservable<long> MutableInterval(IObservable<TimeSpan> period, IScheduler scheduler)
    {
        return period.Select(timespan => Observable.Interval(timespan, scheduler))
                     .Switch()
                     .Scan(0L, (a, _) => a + 1);
    }

这是一个缓慢下降期的测试:

 var slowlyDecreasing =
                Observable.Interval(TimeSpan.FromSeconds(1))
                          .StartWith(0)
                          .Select(p => TimeSpan.FromMilliseconds(1000 / (p + 1)))
                          .Do(p => Console.WriteLine("Period changed to {0}", p)); 
MutableInterval(slowlyDecreasing, Scheduler.Default).Subscribe(Console.WriteLine);