C# 反应式扩展是否支持滚动缓冲区?

C# 反应式扩展是否支持滚动缓冲区?,c#,.net,buffer,system.reactive,sliding-window,C#,.net,Buffer,System.reactive,Sliding Window,我使用反应式扩展将数据整理到100ms的缓冲区中: this.subscription = this.dataService .Where(x => !string.Equals("FOO", x.Key.Source)) .Buffer(TimeSpan.FromMilliseconds(100)) .ObserveOn(this.dispatcherService) .Where(x => x.Count != 0) .Subscribe(

我使用反应式扩展将数据整理到100ms的缓冲区中:

this.subscription = this.dataService
    .Where(x => !string.Equals("FOO", x.Key.Source))
    .Buffer(TimeSpan.FromMilliseconds(100))
    .ObserveOn(this.dispatcherService)
    .Where(x => x.Count != 0)
    .Subscribe(this.OnBufferReceived);
这个很好用。但是,我想要的行为与
Buffer
操作提供的行为稍有不同。基本上,如果接收到另一个数据项,我想重置计时器。只有在整个100毫秒内没有收到数据时,我才想处理它。这就打开了从不处理数据的可能性,因此我还应该能够指定最大计数。我可以想象这样的情景:

.SlidingBuffer(TimeSpan.FromMilliseconds(100), 10000)

我四处看看,在Rx里找不到类似的东西?有人能证实/否认这一点吗?

我写了一个扩展来完成大部分您想要做的事情-
bufferwithinactive

这是:

public static IObservable<IEnumerable<T>> BufferWithInactivity<T>(
    this IObservable<T> source,
    TimeSpan inactivity,
    int maximumBufferSize)
{
    return Observable.Create<IEnumerable<T>>(o =>
    {
        var gate = new object();
        var buffer = new List<T>();
        var mutable = new SerialDisposable();
        var subscription = (IDisposable)null;
        var scheduler = Scheduler.ThreadPool;

        Action dump = () =>
        {
            var bts = buffer.ToArray();
            buffer = new List<T>();
            if (o != null)
            {
                o.OnNext(bts);
            }
        };

        Action dispose = () =>
        {
            if (subscription != null)
            {
                subscription.Dispose();
            }
            mutable.Dispose();
        };

        Action<Action<IObserver<IEnumerable<T>>>> onErrorOrCompleted =
            onAction =>
            {
                lock (gate)
                {
                    dispose();
                    dump();
                    if (o != null)
                    {
                        onAction(o);
                    }
                }
            };

        Action<Exception> onError = ex =>
            onErrorOrCompleted(x => x.OnError(ex));

        Action onCompleted = () => onErrorOrCompleted(x => x.OnCompleted());

        Action<T> onNext = t =>
        {
            lock (gate)
            {
                buffer.Add(t);
                if (buffer.Count == maximumBufferSize)
                {
                    dump();
                    mutable.Disposable = Disposable.Empty;
                }
                else
                {
                    mutable.Disposable = scheduler.Schedule(inactivity, () =>
                    {
                        lock (gate)
                        {
                            dump();
                        }
                    });
                }
            }
        };

        subscription =
            source
                .ObserveOn(scheduler)
                .Subscribe(onNext, onError, onCompleted);

        return () =>
        {
            lock (gate)
            {
                o = null;
                dispose();
            }
        };
    });
}
不活动的公共静态IObservable缓冲区(
这是一个可观测的来源,
时间跨度不活动,
int最大缓冲区大小)
{
返回可观察的。创建(o=>
{
var gate=新对象();
var buffer=新列表();
var mutable=new SerialDisposable();
var subscription=(IDisposable)null;
var scheduler=scheduler.ThreadPool;
动作转储=()=>
{
var bts=buffer.ToArray();
buffer=新列表();
如果(o!=null)
{
o、 OnNext(bts);
}
};
操作处置=()=>
{
if(订阅!=null)
{
subscription.Dispose();
}
mutable.Dispose();
};
行动已完成=
onAction=>
{
锁(门)
{
处置();
dump();
如果(o!=null)
{
行动(o);
}
}
};
操作onError=ex=>
OnError或completed(x=>x.OnError(ex));
操作onCompleted=()=>onerror或completed(x=>x.onCompleted());
操作onNext=t=>
{
锁(门)
{
缓冲区。添加(t);
if(buffer.Count==maximumBufferSize)
{
dump();
可变。一次性=一次性。空;
}
其他的
{
mutable.Disposable=scheduler.Schedule(不活动,()=>
{
锁(门)
{
dump();
}
});
}
}
};
订阅=
来源
.ObserveOn(调度程序)
.认购(onNext、onError、onCompleted);
return()=>
{
锁(门)
{
o=零;
处置();
}
};
});
}

我想这可以在缓冲区方法之上实现,如下所示:

public static IObservable<IList<T>> SlidingBuffer<T>(this IObservable<T> obs, TimeSpan span, int max)
        {
            return Observable.CreateWithDisposable<IList<T>>(cl =>
            {
                var acc = new List<T>();
                return obs.Buffer(span)
                        .Subscribe(next =>
                        {
                            if (next.Count == 0) //no activity in time span
                            {
                                cl.OnNext(acc);
                                acc.Clear();
                            }
                            else
                            {
                                acc.AddRange(next);
                                if (acc.Count >= max) //max items collected
                                {
                                    cl.OnNext(acc);
                                    acc.Clear();
                                }
                            }
                        }, err => cl.OnError(err), () => { cl.OnNext(acc); cl.OnCompleted(); });
            });
        }
publicstaticiobservable滑动缓冲区(此IObservable obs,TimeSpan,int max)
{
返回可观察的。CreateWithDisposable(cl=>
{
var acc=新列表();
返回对象缓冲区(span)
.订阅(下一步=>
{
if(next.Count==0)//在时间跨度内没有活动
{
cl.OnNext(acc);
acc.Clear();
}
其他的
{
acc.AddRange(下一个);
如果(acc.Count>=max)//收集的最大项目数
{
cl.OnNext(acc);
acc.Clear();
}
}
},err=>cl.OnError(err),()=>{cl.OnNext(acc);cl.OnCompleted();});
});
}

注意:我还没有测试过它,但我希望它能给你一个想法。

这可以通过结合内置的
窗口
节流
方法来实现。首先,让我们解决一个更简单的问题,忽略最大计数条件:

public static IObservable<IList<T>> BufferUntilInactive<T>(this IObservable<T> stream, TimeSpan delay)
{
    var closes = stream.Throttle(delay);
    return stream.Window(() => closes).SelectMany(window => window.ToList());
}
我会在我的博客上写一篇文章解释这一点

窗口方法的文档:


使用Rx Extensions 2.0,您可以通过接受超时和大小的新缓冲区过载来满足这两个要求:

this.subscription = this.dataService
    .Where(x => !string.Equals("FOO", x.Key.Source))
    .Buffer(TimeSpan.FromMilliseconds(100), 1)
    .ObserveOn(this.dispatcherService)
    .Where(x => x.Count != 0)
    .Subscribe(this.OnBufferReceived);

有关文档,请参阅。

这里是
BufferUntilInactive
运算符的另一个实现:

/// <summary>
/// Projects each element of an observable sequence into a buffer that's sent out
/// when either a given inactivity timespan has elapsed, or it's full.
/// </summary>
public static IObservable<IList<T>> BufferUntilInactive<T>(
    this IObservable<T> source, TimeSpan dueTime, int maxCount)
{
    return source.Publish(published =>
    {
        var windowReplay = new ReplaySubject<IObservable<T>>(1);
        var overflow = windowReplay
            .SelectMany(window => window.Take(maxCount).Count())
            .Where(windowCount => windowCount == maxCount)
            .Select(_ => Unit.Default);
        return published
            .Window(() => published
                .Throttle(dueTime)
                .Select(_ => Unit.Default)
                .Merge(overflow)
            )
            .Do(window => windowReplay.OnNext(window))
            .SelectMany(window => window.ToList());
    });
}
//
///将可观察序列的每个元素投影到发送的缓冲区中
///当给定的非活动时间间隔已过或已满时。
/// 
公共静态IObservable BufferUntilliActive(
此IObservable源,TimeSpan dueTime,int maxCount)
{
返回source.Publish(published=>
{
var windowReplay=新的ReplaySubject(1);
var overflow=windowReplay
.SelectMany(window=>window.Take(maxCount.Count())
.Where(windowCount=>windowCount==maxCount)
.Select(=>Unit.Default);
已出版的报税表
.Window(()=>已发布
.节气门(双时间)
.Select(=>Unit.Default)
.合并(溢出)
)
.Do(窗口=>windowReplay.OnNext(窗口))
.SelectMany(window=>window.ToList());
});
}

我确信我在Rx的一个教程视频中看到过这种行为,但我恐怕记不起是什么或确切的位置了:(啊,throttle()是我一直在想的,但我不认为它本身就是你想要的。不确定是否有某种方法可以将它结合起来做你想要的…+1谢谢。你写这篇文章是为了这个问题还是为了你的问题
/// <summary>
/// Projects each element of an observable sequence into a buffer that's sent out
/// when either a given inactivity timespan has elapsed, or it's full.
/// </summary>
public static IObservable<IList<T>> BufferUntilInactive<T>(
    this IObservable<T> source, TimeSpan dueTime, int maxCount)
{
    return source.Publish(published =>
    {
        var windowReplay = new ReplaySubject<IObservable<T>>(1);
        var overflow = windowReplay
            .SelectMany(window => window.Take(maxCount).Count())
            .Where(windowCount => windowCount == maxCount)
            .Select(_ => Unit.Default);
        return published
            .Window(() => published
                .Throttle(dueTime)
                .Select(_ => Unit.Default)
                .Merge(overflow)
            )
            .Do(window => windowReplay.OnNext(window))
            .SelectMany(window => window.ToList());
    });
}