簡體   English   中英

Rx IObservable緩沖以平滑突發事件

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

我有一個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.過於復雜的方法

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反射來讀取這些代碼,並用它作為我的函數的基礎上)。 of code. 不幸的是,許多樣板邏輯如AnonymousObservable在system.reactive代碼之外是不可公開訪問的,所以我不得不復制並粘貼代碼。 這個解決方案似乎有效,但考慮到它的復雜性,我對它的bug沒有信心。

我無法相信沒有辦法使用標准的Reactive擴展的某些組合來實現這一點。 我討厭感覺我不必要地重新發明輪子,我試圖建立的模式似乎是一個相當標准的模式。

這實際上是以偶數間隔推送緩沖事件的方式的副本,但我將在這里包含一個摘要(原始看起來很混亂,因為它看起來有幾個選擇)。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM