观察其他观察者看不到的价值

时间:2020-03-02 05:49:41

标签: c# system.reactive

我有一个观察对象,它发出唯一的值,例如

var source=Observable.Range(1,100).Publish();
source.Connect();

我想从例如两个观察者,但每个观察者仅针对其他观察者未看到的值得到通知。

因此,如果第一个观察者包含值10,则第二个观察者永远都不会收到有关该10值的通知。

更新

我选择了@Asti的答案,因为它是第一位的,尽管有错误,但它指出了正确的方向,并投票赞成@Shlomo的答案。太糟糕了,我无法接受两个答案,因为@Shlomo答案更正确,我非常感谢他在此标签上提供的所有帮助。

3 个答案:

答案 0 :(得分:3)

对于不同的观察者,可观察到的行为不应有不同;更好的方法是为每个观察者提供自己的过滤后的可观察物。

话虽如此,如果您的约束条件要求您在单个可观察的状态下需要此行为-我们可以使用Round-Robin方法。

    public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source)
    {
        for (; ; )
            foreach (var item in source.ToArray())
                yield return item;
    }

    public static IObservable<T> RoundRobin<T>(this IObservable<T> source)
    {
        var subscribers = new List<IObserver<T>>();
        var shared = source
            .Zip(subscribers.Repeat(), (value, observer) => (value, observer))
            .Publish()
            .RefCount();

        return Observable.Create<T>(observer =>
        {
            subscribers.Add(observer);
            var subscription = 
                shared
                .Where(pair => pair.observer == observer)
                .Select(pair => pair.value)
                .Subscribe(observer);

            var dispose = Disposable.Create(() => subscribers.Remove(observer));
            return new CompositeDisposable(subscription, dispose);
        });
    }

用法:

var source = Observable.Range(1, 100).Publish();
var dist = source.RoundRobin();
dist.Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Subscribe(i => Console.WriteLine($"Two sees {i}"));

source.Connect();

结果:

One sees 1
Two sees 2
One sees 3
Two sees 4
One sees 5
Two sees 6
One sees 7
Two sees 8
One sees 9
Two sees 10

如果您已经有观察者列表,那么代码将变得更加简单。

答案 1 :(得分:3)

编辑:@Asti修复了他的错误,我根据他的回答修复了我的错误。现在我们的答案大致相似。我有一个想法,如何做一个纯粹的反应性的,如果有时间我会在以后发布。

固定代码:

public static IObservable<T> RoundRobin2<T>(this IObservable<T> source)
{
    var subscribers = new BehaviorSubject<ImmutableList<IObserver<T>>>(ImmutableList<IObserver<T>>.Empty);
    ImmutableList<IObserver<T>> latest = ImmutableList<IObserver<T>>.Empty;
    subscribers.Subscribe(l => latest = l);

    var shared = source
            .Select((v, i) => (v, i))
            .WithLatestFrom(subscribers, (t, s) => (t.v, t.i, s))
            .Publish()
            .RefCount();
    return Observable.Create<T>(observer =>
    {
        subscribers.OnNext(latest.Add(observer));
        var dispose = Disposable.Create(() => subscribers.OnNext(latest.Remove(observer)));

        var sub = shared
            .Where(t => t.i % t.s.Count == t.s.FindIndex(o => o == observer))
            .Select(t => t.v)
            .Subscribe(observer);

        return new CompositeDisposable(dispose, sub);
    });
}

原始答案: 我赞成@Asti的回答,因为他基本上是正确的:仅仅因为可以,并不意味着你应该这样做。他的回答在很大程度上有效,但存在错误:

这很好:

var source = Observable.Range(1, 20).Publish();
var dist = source.RoundRobin();
dist.Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Take(1).Subscribe(i => Console.WriteLine($"Two sees {i}"));

这不是:

var source = Observable.Range(1, 20).Publish();
var dist = source.RoundRobin();
dist.Take(1).Subscribe(i => Console.WriteLine($"One sees {i}"));
dist.Subscribe(i => Console.WriteLine($"Two sees {i}"));

输出为:

One sees 1
Two sees 1
Two sees 2
Two sees 3
Two sees 4
...

我最初认为该错误是Halloween related,但现在不确定。 .ToArray()中的Repeat应该注意这一点。我还编写了一个纯净的,可观察的实现,该实现具有相同的错误。此实现并不能保证完美的Round Robin,但这不是问题所在:

public static IObservable<T> RoundRobin2<T>(this IObservable<T> source)
{
    var subscribers = new BehaviorSubject<ImmutableList<IObserver<T>>>(ImmutableList<IObserver<T>>.Empty);
    ImmutableList<IObserver<T>> latest = ImmutableList<IObserver<T>>.Empty;
    subscribers.Subscribe(l => latest = l);

    var shared = source
            .Select((v, i) => (v, i))
            .WithLatestFrom(subscribers, (t, s) => (t.v, t.i, s))
            .Publish()
            .RefCount();
    return Observable.Create<T>(observer =>
    {
        subscribers.OnNext(latest.Add(observer));
        var dispose = Disposable.Create(() => subscribers.OnNext(latest.Remove(observer)));

        var sub = shared
            .Where(t => t.i % t.s.Count == t.s.FindIndex(o => o == observer))
            .Select(t => t.v)
            .Subscribe(observer);

        return new CompositeDisposable(dispose, sub);
    });
}

答案 2 :(得分:2)

这是使用TPL Dataflow的简单分布式队列实现。但是对于不同的观察者看不到相同的值,几乎没有机会表现不正确。它不是轮循机制,但实际上具有反压语义。

    public static IObservable<T> Distribute<T>(this IObservable<T> source)
    {
        var buffer = new BufferBlock<T>();
        source.Subscribe(buffer.AsObserver());             
        return Observable.Create<T>(observer =>
            buffer.LinkTo(new ActionBlock<T>(observer.OnNext, new ExecutionDataflowBlockOptions { BoundedCapacity = 1 })
        );
    }

输出

One sees 1
Two sees 2
One sees 3
Two sees 4
One sees 5
One sees 6
One sees 7
One sees 8
One sees 9
One sees 10

我可能更喜欢完全跳过Rx,而仅使用TPL Dataflow。