假脱机Observable.FromEvent生成的正在进行的项目

时间:2018-12-19 15:51:32

标签: c# multithreading system.reactive

我在这里的目标是为以后的订阅者缓存从IObservable<T>开始的所有项目/通知。

例如如果某人订阅了消息流,则首先他会接收订阅之前的所有消息。然后,只要有任何消息,他就开始接收新消息。这应该无缝发生,在新旧消息之间的“边界”上没有重复和损失。

我想出了以下扩展方法:

public static IObservable<T> WithHistory<T>(this IObservable<T> source)
{
    var accumulator = new BlockingCollection<T>();

    source.Subscribe(accumulator.Add);

    return accumulator
        .GetConsumingEnumerable()
        .ToObservable()
        .SubscribeOn(ThreadPoolScheduler.Instance);
}

据我测试,它可以正常工作:

class Generator<T>
{
    event Action<T> onPush;

    public IObservable<T> Items =>
        Observable.FromEvent<T>(d => onPush += d, d => onPush -= d);

    public void Push(T item) => onPush?.Invoke(item);
}

...

private static void Main()
{
    var g = new Generator<int>();
    var ongoingItems = g.Items;
    var allItems = g.Items.WithHistory();

    g.Push(1);
    g.Push(2);

    ongoingItems.Subscribe(x => Console.WriteLine($"Ongoing: got {x}"));
    allItems.Subscribe(x => Console.WriteLine($"WithHistory: got {x}"));

    g.Push(3);
    g.Push(4);
    g.Push(5);

    Console.ReadLine();
}

结果:

Ongoing: got 3
Ongoing: got 4
Ongoing: got 5
WithHistory: got 1
WithHistory: got 2
WithHistory: got 3
WithHistory: got 4
WithHistory: got 5

但是,使用BlockingCollection<T>似乎有点过分。同样,上述方法不支持完成操作,错误处理,并且如果没有.SubscribeOn(ThreadPoolScheduler.Instance),则会导致死锁。

没有描述的缺陷,有没有更好的方法来实现它?

1 个答案:

答案 0 :(得分:2)

最好的方法是使用candidates

.Replay()

void Main() { var g = new Generator<int>(); var ongoingItems = g.Items; var allItems = g.Items.Replay().RefCount(); using(var tempSubscriber = allItems.Subscribe()) { g.Push(1); g.Push(2); ongoingItems.Subscribe(x => Console.WriteLine($"Ongoing: got {x}")); allItems.Subscribe(x => Console.WriteLine($"WithHistory: got {x}")); g.Push(3); g.Push(4); g.Push(5); Console.ReadLine(); } } 产生一个可观察的对象,只要有订阅者,它就会保留一个内部队列以供重播。但是,如果您有一个持久的订户(就像您的解决方案在.Replay().RefCount()方法中一样),那么就会发生内存泄漏。解决此问题的最佳方法是拥有一个临时订阅者,当您不再对历史记录感兴趣后,该订阅者会自动断开连接。