Rx IObservable缓冲以平滑突发事件

时间:2010-12-22 01:45:44

标签: c# buffering system.reactive

我有一个Observable序列,可以快速突发产生事件(即:一个接一个地发生五个事件,然后是长时间延迟,然后是另一个快速突发事件等)。我希望通过在事件之间插入一个短暂的延迟来平滑这些突发。想象一下以下图表作为示例:

Raw:      --oooo--------------ooooo-----oo----------------ooo|
Buffered: --o--o--o--o--------o--o--o--o--o--o--o---------o--o--o|

我目前的方法是通过Observable.Interval()生成一个类似节拍器的计时器,该计时器表示何时可以从原始流中提取另一个事件。问题是我无法弄清楚如何将该计时器与我的原始无缓冲可观察序列结合起来。

IObservable.Zip()接近于我想做的事情,但它只有在原始流比定时器生成事件更快的情况下才有效。一旦原始流中存在显着的间歇,计时器就会建立一系列不需要的事件,然后立即与原始流中的下一个事件突发事件配对。

理想情况下,我想要一个带有以下函数签名的IObservable扩展方法,它可以生成我上面概述的bevaior。现在,来救援StackOverflow:)

public static IObservable<T> Buffered(this IObservable<T> src, TimeSpan minDelay)

PS。我对Rx来说是全新的,所以如果这是一个简单的问题,我很抱歉......


1。简单而有缺陷的方法

这是我最初的天真和简单的解决方案,有很多问题:

public static IObservable<T> Buffered<T>(this IObservable<T> source, TimeSpan minDelay)
{
    Queue<T> q = new Queue<T>();
    source.Subscribe(x => q.Enqueue(x));
    return Observable.Interval(minDelay).Where(_ => q.Count > 0).Select(_ => q.Dequeue());
}

第一个明显的问题是内部订阅返回到原始源的IDisposable丢失,因此订阅无法终止。在此方法返回的IDisposable上调用Dispose会终止计时器,但不会触发现在不必要地填充队列的基础原始事件源,而没有人留下来从队列中提取事件。

第二个问题是,从原始事件流到缓冲流,无法通过异常或流末尾通知传播 - 在订阅原始源时,它们将被忽略。

最后但并非最不重要的是,现在我已经有了定期唤醒的代码,无论是否有任何工作要做,我宁愿在这个美好的新反应世界中避免。


2。过于复杂的方式

为了解决我最初的简单化方法遇到的问题,我编写了一个很多更复杂的函数,其行为与IObservable.Delay()非常相似(我使用.NET Reflector来读取该代码并使用它作为我的功能的基础)。不幸的是,许多样板逻辑(例如AnonymousObservable)在system.reactive代码之外是不可公开访问的,因此我不得不复制并粘贴 lot 代码。这个解决方案似乎有效,但考虑到它的复杂性,我对它的无bug没有信心。

我无法相信使用标准Reactive扩展的某些组合无法实现此目的。我讨厌感觉我不必要地重新发明轮子,而我正在尝试构建的模式似乎是一个相当标准的模式。

1 个答案:

答案 0 :(得分:10)

这实际上是A way to push buffered events in even intervals的副本,但我会在这里添加一个摘要(原文看起来很混乱,因为它看了几个选项)。

public static IObservable<T> Buffered<T>(this IObservable<T> source, TimeSpan minDelay)
{
    return source.Drain(x => 
        Observable.Empty<int>()
            .Delay(minDelay)
            .StartWith(x)
    );
}

我的Drain实现类似于SelectMany,但它等待先前的输出完成(您可以将其视为ConactMany,而SelectMany更像MergeMany }})。内置的Drain无法正常工作,因此您需要在下面添加以下内容:

public static class ObservableDrainExtensions
{
    public static IObservable<TOut> Drain<TSource, TOut>(
        this IObservable<TSource> source, 
        Func<TSource, IObservable<TOut>> selector)
    {
        return Observable.Defer(() =>
        {
            BehaviorSubject<Unit> queue = new BehaviorSubject<Unit>(new Unit());

            return source
                .Zip(queue, (v, q) => v)
                .SelectMany(v => selector(v)
                    .Do(_ => { }, () => queue.OnNext(new Unit()))
                );
        });
    }
}