如何在Rx中异步重新过滤序列

时间:2016-03-10 10:01:51

标签: vb.net system.reactive reactive-programming

我有一个可观察的序列,应根据某些条件使用Where运算符进行过滤,并应使用这些过滤的元素填充列表。

我想动态更改过滤条件,因此当发生这种情况时,应清除列表,应评估可观察序列的先前元素的新标准,以及序列生成的新元素。

列表应该使用过去的元素重新填充,这些元素现在通过新标准,继续使用新元素(也可以过滤)。我并不关心新元素可能会被延迟,而之前的元素会被重新评估,但我确实关心订单。

这可以通过Reactive Extensions完成吗?

enter image description here

3 个答案:

答案 0 :(得分:3)

这是一个很好的扩展方法,可以满足您的需求:

public static IObservable<T> Refilterable<T>(
    this IObservable<T> source, IObservable<Func<T, bool>> filters)
{
    return
        Observable
            .Create<T>(o =>
            {
                var replay = new ReplaySubject<T>();
                var replaySubscription = source.Subscribe(replay);
                var query = filters.Select(f => replay.Where(f)).Switch();
                var querySubscription = query.Subscribe(o);
                return new CompositeDisposable(replaySubscription, querySubscription);
            });
}

我用这段代码测试了这个:

var source = new Subject<int>();
var filters = new Subject<Func<int, bool>>();

var subscription = source.Refilterable(filters).Subscribe(x => Console.WriteLine(x));

source.OnNext(1);
source.OnNext(2);
source.OnNext(3);

filters.OnNext(x => x % 2 == 0);

source.OnNext(4);
source.OnNext(5);

filters.OnNext(x => x % 2 == 1);

source.OnNext(6);

filters.OnNext(x => x % 3 == 0);

source.OnNext(7);

filters.OnNext(x => x % 2 == 1);

subscription.Dispose();

filters.OnNext(x => x % 2 == 0);

我得到了这个输出:

2
4
1
3
5
3
6
1
3
5
7

这似乎是你追求的目标。

我刚刚注意到了生成列表的要求。这是一个更新:

public static IObservable<IList<T>> Refilterable<T>(this IObservable<T> source, IObservable<Func<T, bool>> filters)
{
    return
        Observable
            .Create<IList<T>>(o =>
            {
                var replay = new ReplaySubject<T>();
                var replaySubscription = source.Subscribe(replay);
                var query =
                    filters
                        .Select(f =>
                            replay
                                .Synchronize()
                                .Where(f)
                                .Scan(new List<T>(), (a, x) =>
                                {
                                    a.Add(x);
                                    return a;
                                }))
                        .Switch();
                var querySubscription = query.Subscribe(o);
                return new CompositeDisposable(replaySubscription, querySubscription);
            });
}

我注意到的另一件事是VB.NET标签。如果需要,我会看看我是否可以转换。

这应该是正确的VB:

<System.Runtime.CompilerServices.Extension> _
Public Shared Function Refilterable(Of T)(source As IObservable(Of T), filters As IObservable(Of Func(Of T, Boolean))) As IObservable(Of IList(Of T))
    Return Observable.Create(Of IList(Of T))( _
        Function(o)
            Dim replay = New ReplaySubject(Of T)()
            Dim replaySubscription = source.Subscribe(replay)
            Dim query = filters.[Select](Function(f) replay.Synchronize().Where(f).Scan(New List(Of T)(), _
                Function(a, x)
                    a.Add(x)
                    Return a
                End Function)).Switch()
            Dim querySubscription = query.Subscribe(o)
            Return New CompositeDisposable(replaySubscription, querySubscription)
        End Function)
End Function

答案 1 :(得分:1)

除非我遗漏了一些重要的内容,否则指定的需求将消除对不可变集合的需求并简化实现,因为您需要缓冲所有发出的值。

private List<T> values = new List<T>();
private IObservable<T> _valueSource;
public List<T> ValidValues => values.Where(MatchesCriteria).ToList();

private void StartSubscriptions()
{
    var addNewValuesSub = _valueSource.Subscribe(values.Add); //todo disposing
}

如果您认为IEnumerable.Where太慢,我们事先知道所有可能的标准。我们可以GroupBy将值分别添加到各自的Observable s /数据结构中。它看起来像这样。

_valueSource.GroupBy(CriteriaSelector)
            .Subscribe(i => UpdateDataStructure(i.Key(), i.Latest()) );

IObservable不适合缓冲大量值,以后可以通过某些条件访问这些值。这是IEnumerable的工作。

最后,如果您认为内存使用会成为问题,请考虑将values重构为内存对象缓存系统。

答案 2 :(得分:1)

我有两个初步反应

  1. 这只是在过滤条件发生变化时重新订阅的。Replay()
  2. 这听起来像是一个无限的缓冲区: - /
  3. 为了解决我的第一个想法,你可以拥有这个代码

    public class Foo
    {
        private readonly IDisposable _replayConnection;
        private readonly IConnectableObservable<int> _replaySource;
        private readonly SerialDisposable _subscription = new SerialDisposable();
        private readonly List<int> _values = new List<int>();
    
        //the Ctor or some initialise method...
        public Foo(IObservable<int> source)
        {
            _replaySource = source.Replay();
            _replayConnection =  _replaySource.Connect()
        }
    
        public void SetFilter(Func<int, bool> predicate)
        {
            //Not thread safe. If required, then a scheduler can solve that.
            _values.Clear();
            _subscription.Disposable = _replaySource.Where(predicate)
                .Subscribe(value => _values.Add(value),
                    ex => {/*Do something here to handle errors*/},
                    () => {/*Do something here if you want to cater for completion of the sequence*/},
        }
    }
    

    然而,这只是让我更关注点 2 。如果您期望数百万的值,那么如果它们只是整数,那么您将使用大约3MB / 1mil的内存项。如果它们是整数,则按值语义复制,您将在重放缓冲区和最终列表(IIRC)中获得副本。如果这种内存压力还可以,那么我认为上面的Replay代码会没问题。另请注意,如果您尝试缓冲超过int.MaxValue值,则会抛出此重播用法。