简体   繁体   English

Rx IObservable缓冲以平滑突发事件

[英]Rx IObservable buffering to smooth out bursts of events

I have an Observable sequence that produces events in rapid bursts (ie: five events one right after another, then a long delay, then another quick burst of events, etc.). 我有一个Observable序列,可以快速突发产生事件(即:一个接一个地发生五个事件,然后是长时间延迟,然后是另一个快速突发事件等)。 I want to smooth out these bursts by inserting a short delay between events. 我希望通过在事件之间插入一个短暂的延迟来平滑这些突发。 Imagine the following diagram as an example: 想象一下以下图表作为示例:

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

My current approach is to generate a metronome-like timer via Observable.Interval() that signals when it's ok to pull another event from the raw stream. 我目前的方法是通过Observable.Interval()生成类似节拍器的计时器,该计时器表示何时可以从原始流中拉出另一个事件。 The problem is that I can't figure out how to then combine that timer with my raw unbuffered observable sequence. 问题是我无法弄清楚如何将该计时器与我的原始无缓冲可观察序列相结合。

IObservable.Zip() is close to doing what I want, but it only works so long as the raw stream is producing events faster than the timer. IObservable.Zip()接近于我想做的事情,但它只有在原始流比定时器生成事件更快的情况下才有效。 As soon as there is a significant lull in the raw stream, the timer builds up a series of unwanted events that then immediately pair up with the next burst of events from the raw stream. 一旦原始流中存在显着的间歇,计时器就会建立一系列不需要的事件,然后立即与原始流中的下一个事件突发事件配对。

Ideally, I want an IObservable extension method with the following function signature that produces the bevaior I've outlined above. 理想情况下,我想要一个具有以下函数签名的IObservable扩展方法,该方法生成我上面概述的bevaior。 Now, come to my rescue StackOverflow :) 现在,来救我的StackOverflow :)

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

PS. PS。 I'm brand new to Rx, so my apologies if this is a trivially simple question... 我是Rx的新手,所以如果这是一个简单的简单问题,我很抱歉......


1. Simple yet flawed approach 1.简单而有缺陷的方法

Here's my initial naive and simplistic solution that has quite a few problems: 这是我最初的天真和简单的解决方案,它有很多问题:

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());
}

The first obvious problem with this is that the IDisposable returned by the inner subscription to the raw source is lost and therefore the subscription can't be terminated. 第一个明显的问题是内部订阅返回到原始源的IDisposable丢失,因此订阅无法终止。 Calling Dispose on the IDisposable returned by this method kills the timer, but not the underlying raw event feed that is now needlessly filling the queue with nobody left to pull events from the queue. 在此方法返回的IDisposable上调用Dispose会终止计时器,但不会触发现在不必要地填充队列的基础原始事件源,没有人从队列中提取事件。

The second problem is that there's no way for exceptions or end-of-stream notifications to be propogated through from the raw event stream to the buffered stream - they are simply ignored when subscribing to the raw source. 第二个问题是,从原始事件流到缓冲流,无法通过异常或流末尾通知进行传播 - 在订阅原始源时,它们将被忽略。

And last but not least, now I've got code that wakes up periodically regardless of whether there is actually any work to do, which I'd prefer to avoid in this wonderful new reactive world. 最后但并非最不重要的是,现在我已经有了定期唤醒的代码,无论是否有任何工作要做,我宁愿避免在这个美妙的新反应世界中。


2. Way overly complex appoach 2.过于复杂的方法

To solve the problems encountered in my initial simplistic approach, I wrote a much more complicated function that behaves much like IObservable.Delay() (I used .NET Reflector to read that code and used it as the basis of my function). 为了解决我最初的简单的方法遇到的问题,我写了一个复杂的功能,其行为很像IObservable.Delay()我用.net反射来读取这些代码,并用它作为我的函数的基础上)。 Unfortunately, a lot of the boilerplate logic such as AnonymousObservable is not publicly accessible outside the system.reactive code, so I had to copy and paste a lot of code. 不幸的是,许多样板逻辑如AnonymousObservable在system.reactive代码之外是不可公开访问的,所以我不得不复制并粘贴大量代码。 This solution appears to work, but given its complexity, I'm less confident that its bug free. 这个解决方案似乎有效,但考虑到它的复杂性,我对它的bug没有信心。

I just can't believe that there isn't a way to accomplish this using some combination of the standard Reactive extensions. 我无法相信没有办法使用标准的Reactive扩展的某些组合来实现这一点。 I hate feeling like I'm needlessly reinventing the wheel, and the pattern I'm trying to build seems like a fairly standard one. 我讨厌感觉我不必要地重新发明轮子,我试图建立的模式似乎是一个相当标准的模式。

This is actually a duplicate of A way to push buffered events in even intervals , but I'll include a summary here (the original looks quite confusing because it looks at a few alternatives). 这实际上是以偶数间隔推送缓冲事件的方式的副本,但我将在这里包含一个摘要(原始看起来很混乱,因为它看起来有几个选择)。

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

My implementation of Drain works like SelectMany , except it waits for the previous output to finish first (you could think of it as ConactMany , whereas SelectMany is more like MergeMany ). 我的Drain实现就像SelectMany一样,除了它等待先前的输出完成(你可以把它想象成ConactMany ,而SelectMany更像是MergeMany )。 The built-in Drain does not work this way, so you'll need to include the implementation below: 内置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()))
                );
        });
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM