C# IObservable TakeLast(n)和阻塞

C# IObservable TakeLast(n)和阻塞,c#,.net,system.reactive,C#,.net,System.reactive,我试图得到一个可观察的对象的20个最新值,并将其作为一个属性公开,而不阻塞它的发生。目前,我的代码如下所示: class Foo { private IObservable<int> observable; public Foo(IObservable<int> bar) { this.observable = bar; } public IEnumerable<int> MostRecentBars

我试图得到一个可观察的对象的20个最新值,并将其作为一个属性公开,而不阻塞它的发生。目前,我的代码如下所示:

class Foo
{
    private IObservable<int> observable;

    public Foo(IObservable<int> bar)
    {
        this.observable = bar;
    }

    public IEnumerable<int> MostRecentBars
    {
        get 
        {
             return this.observable.TakeLast(20).ToEnumerable();
        }
    }
 }
class-Foo
{
私有的可观察的;
公共食物(可观察的酒吧)
{
这。可观察=巴;
}
公共IEnumerableMostrecentBars
{
得到
{
返回this.observable.TakeLast(20.ToEnumerable();
}
}
}
然而,当调用mostrecentbar getter时,这是阻塞,可能是因为ToEnumerable在至少有20个观察值之前不会返回


是否有一种内置的方式可以在不阻塞的情况下最多暴露20个可观测的最新值?如果观察到的值少于20个,那么它应该只返回所有值。

我想不出一个符合您要求的内置Rx操作员。您可以通过以下方式实现它:

class Foo
{
    private IObservable<int> observable;
    private Queue<int> buffer = new Queue<int>();

    public Foo(IObservable<int> bar)
    {
        this.observable = bar;

        this.observable
            .Subscribe(item =>
            {
                lock (buffer)
                {
                    if (buffer.Count == 20) buffer.Dequeue();
                    buffer.Enqueue(item);
                }
            });
    }

    public IEnumerable<int> MostRecentBars
    {
        get
        {
            lock (buffer)
            { 
                return buffer.ToList();     // Create a copy.
            }
        }
    }
}
class-Foo
{
私有的可观察的;
专用队列缓冲区=新队列();
公共食物(可观察的酒吧)
{
这。可观察=巴;
这是可以观察到的
.订阅(项目=>
{
锁(缓冲区)
{
如果(buffer.Count==20)buffer.Dequeue();
缓冲区。排队(项目);
}
});
}
公共IEnumerableMostrecentBars
{
得到
{
锁(缓冲区)
{ 
return buffer.ToList();//创建一个副本。
}
}
}
}

我给你两个选择。一种是使用Rx
Scan
操作符,但我认为这会使它的阅读变得更复杂。另一个使用带锁定的标准
队列
。你可以选择

(一)

class-Foo
{
私有int[]条=新int[]{};
公共食物(可观察的酒吧)
{
酒吧
.扫描(
新的int[]{},
(ns,n)=>
ns
.Concat(新[]{n,})
.TakeLast(20)
.ToArray())
.订阅(ns=>bar=ns);
}
公共IEnumerableMostrecentBars
{
得到
{
返回杆;
}
}
}
(二)

class-Foo
{
专用队列=新队列();
公共食物(可观察的酒吧)
{
订阅(n=>
{
锁(队列)
{
排队,排队(n);
如果(queue.Count>20)
{
queue.Dequeue();
}
}
});
}
公共IEnumerableMostrecentBars
{
得到
{
锁(队列)
{
return queue.ToArray();
}
}
}
}

我希望这些帮助。

虽然您已经得到了答案,但我正在考虑使用带缓冲区的Replay主题解决此问题,并提出了如下建议:

class Foo
{
    private ReplaySubject<int> replay = new ReplaySubject<int>(20);

    public Foo(IObservable<int> bar)
    {
        bar.Subscribe(replay);
    }

    public IEnumerable<int> MostRecentBars
    {
        get
        {
            var result = new List<int>();
            replay.Subscribe(result.Add); //Replay fill in the list with buffered items on same thread
            return result;
        }
    }
}
class-Foo
{
私有ReplaySubject replay=新ReplaySubject(20);
公共食物(可观察的酒吧)
{
订阅(重播);
}
公共IEnumerableMostrecentBars
{
得到
{
var result=新列表();
replay.Subscribe(result.Add);//replay使用同一线程上的缓冲项填充列表
返回结果;
}
}
}

让我知道这是否符合你的问题

我倾向于将一些扩展附加到我使用被动扩展构建的任何项目,其中一个是滑动窗口:

public static IObservable<IEnumerable<T>> SlidingWindow<T>(this IObservable<T> o, int length)
{
   Queue<T> window = new Queue<T>();

    return o.Scan<T, IEnumerable<T>>(new T[0], (a, b) =>
    {
        window.Enqueue(b);
        if (window.Count > length)
            window.Dequeue();
        return window.ToArray();
    });
}
publicstaticiobservable滑动窗口(此IObservable o,int-length)
{
队列窗口=新队列();
返回o.Scan(新T[0],(a,b)=>
{
窗口。排队(b);
如果(window.Count>长度)
window.Dequeue();
返回窗口。ToArray();
});
}
这将返回最近N个项目的数组(如果还并没有N个项目,则返回更少的项目)

对于您的情况,您应该能够做到:

class Foo
{
    private IObservable<int> observable;
    private int[] latestWindow = new int[0];

    IDisposable slidingWindowSubscription;

    public Foo(IObservable<int> bar)
    {
        this.observable = bar;
        slidingWindowSubscription = this.observable.SlidingWindow(20).Subscribe(a =>
            {
                latestWindow = a;
            });
    }

    public IEnumerable<int> MostRecentBars
    {
        get 
        {
             return latestWindow;
        }
    }
}
class-Foo
{
私有的可观察的;
private int[]latestWindow=new int[0];
IDisposable slidingWindowSubscription;
公共食物(可观察的酒吧)
{
这。可观察=巴;
slidingWindowSubscription=this.observable.SlidingWindow(20.Subscription)(a=>
{
最晚窗口=a;
});
}
公共IEnumerableMostrecentBars
{
得到
{
返回最晚的窗口;
}
}
}

说真的,这不都取决于你愿意等多久吗?当你手头有19件物品时,你怎么知道可观察物品已经完成?一秒钟后你确定已经完成了?十年后?你确定已经完成了?你怎么知道的?它是可观察的,所以你必须一直观察它,直到你的操作符或者你应用的任何一元变换对传入的流进行一些有用的变换


我认为(可能是新添加的)带有TimeSpan重载的窗口或缓冲区可以工作。尤其是Window,它释放了一个Observable的Observable,因此一旦创建了外部Observable,您就可以实际监听20项中的第一项,然后您需要仔细监听未完成的项,否则您就失去了Window操作符的全部要点,但是你明白了。

在IObservable中没有所谓的TakeLast方法现在看到Ilian Pinzon几乎编写了相同的
队列
实现后,我会选择这种方法作为你的最佳选择。令人惊讶的是,我们的解决方案与此非常接近。直到我发了我的邮件后我才看他的。尽管如此,
Scan
操作符经常被忽略,但它在许多情况下都非常有用。实际上,我更喜欢使用
Scan
的第一种方法,它可以使可观察链继续超出“最后一次(最多)n”。事实上,将其转化为一种扩展方法将是非常棒的
public static IObservable<IEnumerable<T>> SlidingWindow<T>(this IObservable<T> o, int length)
{
   Queue<T> window = new Queue<T>();

    return o.Scan<T, IEnumerable<T>>(new T[0], (a, b) =>
    {
        window.Enqueue(b);
        if (window.Count > length)
            window.Dequeue();
        return window.ToArray();
    });
}
class Foo
{
    private IObservable<int> observable;
    private int[] latestWindow = new int[0];

    IDisposable slidingWindowSubscription;

    public Foo(IObservable<int> bar)
    {
        this.observable = bar;
        slidingWindowSubscription = this.observable.SlidingWindow(20).Subscribe(a =>
            {
                latestWindow = a;
            });
    }

    public IEnumerable<int> MostRecentBars
    {
        get 
        {
             return latestWindow;
        }
    }
}