Reactive的“缓冲直到安静”行为?

时间:2016-02-22 15:22:37

标签: c# system.reactive reactive-programming

我的问题有点像Nagle算法创建的问题,但不完全正确。我想要的是从IObservable< T>缓冲OnNext通知。进入一系列IObservable< IList< T>>如下: 当第一个T通知到达时,将其添加到缓冲区并开始倒计时 如果在倒计时到期之前收到另一个T通知,请将其添加到缓冲区并重新开始倒计时 一旦倒计时结束(即,生产者已经沉默了一段时间),将所有缓冲的T通知转发为单个聚合IList< T>。通知。 如果缓冲区大小在倒计时到期之前超过某个最大值,则无论如何都要发送它。 的IObservable< IList的< T>>缓冲区(此IObservable< T>,Timespan,int,IScheduler)看起来很有希望,但它似乎定期发送聚合通知,而不是“在第一个通知到达时启动计时器并在其他通知到达时重新启动”行为我想,如果没有从下面生成通知,它还会在每个时间窗口的末尾发送一个空列表。 我不想丢弃任何T通知;只是缓冲它们。 有这样的事情存在,还是我需要自己编写?

3 个答案:

答案 0 :(得分:6)

SO上存在一些类似的问题但不完全像这样。 这是一种可以解决问题的扩展方法。

public static IObservable<IList<TSource>> BufferWithThrottle<TSource>
                                          (this IObservable<TSource> source,
                                           int maxAmount, TimeSpan threshold)
{
    return Observable.Create<IList<TSource>>((obs) =>
    {
        return source.GroupByUntil(_ => true,
                                   g => g.Throttle(threshold).Select(_ => Unit.Default)
                                         .Merge( g.Buffer(maxAmount).Select(_ => Unit.Default)))
                     .SelectMany(i => i.ToList())
                     .Subscribe(obs);
    });
}

答案 1 :(得分:6)

有趣的运营商。 Supertopi的答案很好,但是可以做出改进。如果maxAmount很大,和/或通知率很高,那么使用Buffer将通过分配不久之后被丢弃的缓冲区来刻录GC。

为了在达到GroupBy后关闭每个maxAmount Observable,您不需要捕获所有这些元素中的Buffer只是为了知道它何时已满。根据Supertopi的回答,您可以将其略微改为以下内容。它不是收集BuffermaxAmount元素,而是在流上看到maxAmount个元素之后发出信号。

public static IObservable<IList<TSource>> BufferWithThrottle<TSource>(this IObservable<TSource> source, int maxAmount, TimeSpan threshold)
{
    return Observable.Create<IList<TSource>>((obs) =>
    {
        return source.GroupByUntil(_ => true,
                                   g => g.Throttle(threshold).Select(_ => Unit.Default)
                                         .Merge(g.Take(maxAmount)
                                                 .LastAsync()
                                                 .Select(_ => Unit.Default)))
                     .SelectMany(i => i.ToList())
                     .Subscribe(obs);
    });
}

答案 2 :(得分:2)

很好的解决方案。在我看来,使用exisitng运算符创建行为只是为了方便但不是为了性能。

另外,我们应该总是返回IEnumerable而不是IList。 返回最少派生类型(IEnumerable)将为您提供最大的余地,以便在轨道上更改底层实现。

这是我实现自定义运算符的版本。

public static IObservable<IEnumerable<TValue>> BufferWithThrottle<TValue>(this IObservable<TValue> @this, int maxAmount, TimeSpan threshold)
    {
        var buffer = new List<TValue>();

        return Observable.Create<IEnumerable<TValue>>(observer =>
        {
            var aTimer = new Timer();
            void Clear()
            {
                aTimer.Stop();
                buffer.Clear();
            }
            void OnNext()
            {
                observer.OnNext(buffer);
                Clear();
            }
            aTimer.Interval = threshold.TotalMilliseconds;
            aTimer.Enabled = true;
            aTimer.Elapsed += (sender, args) => OnNext();
            var subscription = @this.Subscribe(value =>
            {
                buffer.Add(value);
                if (buffer.Count >= maxAmount)
                    OnNext();
                else
                {
                    aTimer.Stop();
                    aTimer.Start();
                }
            });
            return Disposable.Create(() =>
            {
                Clear();
                subscription.Dispose();
            });
        });
    }

通过测试性能与其他解决方案相比,它可以节省高达30%的CPU功率并解决内存问题。

相关问题