C# 学习Rx:如何使用.Window的输出扫描可观察的布尔值序列

C# 学习Rx:如何使用.Window的输出扫描可观察的布尔值序列,c#,system.reactive,C#,System.reactive,我有一系列真假值,就像这样 var alternatingTrueFalse = Observable.Generate( true, _ => true, x => !x, x => x, _ => TimeSpan.FromMilliseconds(new Random().Next(2000))) .Take

我有一系列真假值,就像这样

        var alternatingTrueFalse = Observable.Generate(
            true,
            _ => true,
            x => !x,
            x => x,
            _ => TimeSpan.FromMilliseconds(new Random().Next(2000)))
            .Take(20).Publish();
        alternatingTrueFalse.Connect();
        var buffered = alternatingTrueFalse
            .Buffer(TimeSpan.FromMilliseconds(500))
            .Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"));
我想看看500毫秒(最大)窗口/缓冲区的顺序。若在一个这样的窗口中只有一个真值(并没有其他值),我想翻转一个开关(只需调用一个命令,现在打印到控制台)。然后,当下一个假值到达时,我想将开关翻转回来,关闭原始序列的当前窗口/缓冲区,然后启动一个新的窗口/缓冲区

使用缓冲+扫描翻转开关 到目前为止,我已经找到了一种在缓冲区上实现这一点的方法。但是,缓冲区打开的时间太长,总是500毫秒

        var buffered = alternatingTrueFalse
            .Buffer(TimeSpan.FromMilliseconds(500));
        var output = buffered
            .Subscribe(x => Console.WriteLine($"{TimeStamp} {string.Join(",", x)}"));

        var isFlipped = buffered.Scan(false, 
                (x, y) => 
                { 
                    if (y.Count == 0)
                    {
                        return x;
                    }
                    return y.Count == 1 && y.First();
                });

        isFlipped.DumpTimes("Flipped");
我正试图找出如何使用窗口而不是缓冲区,以便在一个孤立的true之后,能够将开关翻转回第一个false。但我似乎不能正确地理解它,我还不太精通Rx,也不知道如何使用它的窗口打开/关闭值

示例输出 原创的

2017-10-07 20:21:39.302 True,False   // Rapid values should not flip the switch (actually they should flip a different switch)
2017-10-07 20:21:39.797 True         // Flip the switch here
2017-10-07 20:21:40.302 False        // Flip the switch back and close the current window
2017-10-07 20:21:40.797 True         // Flip the switch here
2017-10-07 20:21:41.297 
2017-10-07 20:21:41.798 False        // Etc...
...
2017-10-07 20:21:43.297 True
2017-10-07 20:21:43.800 False,True   // Example of a window that is open too long, because it is not closed immediately upon the false value
...
缓冲+扫描

2017-10-07 20:47:15.154 True
2017-10-07 20:47:15.163 - Flipped-->True
2017-10-07 20:47:15.659 False,True   // Example of a window open too long
2017-10-07 20:47:15.661 - Flipped-->False

这里有一个不使用
Scan
方法的解决方案

问题似乎是基于两个条件关闭缓冲区-最长时间或特定值。这个是基于


我建议不要基于其他操作符创建自定义操作符,因为这将占用更多的CPU和内存

下面是相同方法的干净版本

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();
        });
    });
}
public static IObservable BufferWithThrottle(this,int maxAmount,TimeSpan阈值下的此IObservable)
{
var buffer=新列表();
返回可观察的。创建(观察者=>
{
var aTimer=新计时器();
无效清除()
{
停止();
buffer.Clear();
}
void OnNext()
{
observer.OnNext(缓冲区);
清除();
}
aTimer.Interval=threshold.total毫秒;
aTimer.Enabled=true;
aTimer.appead+=(发送方,参数)=>OnNext();
var subscription=@this.subscripte(值=>
{
缓冲区。添加(值);
如果(buffer.Count>=maxAmount)
OnNext();
其他的
{
停止();
aTimer.Start();
}
});
返回一次性。创建(()=>
{
清除();
subscription.Dispose();
});
});
}

谢谢,这似乎给了我一个很好的基础来继续。我仍然想了解更多有关窗口和扫描的信息,但groupbyuntill似乎也不错。仅供参考,我在这里的一个更有趣的场景中使用了这个方法:好的,但问题仍然是一样的-基于多个条件关闭缓冲区/窗口。我不确定你能用
Scan
完成什么,因为你不能控制缓冲区/窗口的关闭。也许你可以直接把它应用到源观测上,然后从那里开始构建
alternatingTrueFalse.BufferWithClosingValue( TimeSpan.FromMilliseconds(500), false );
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();
        });
    });
}