Rx在不同的线程上产生和消耗

时间:2014-12-02 11:11:53

标签: c# .net-4.0 system.reactive

我试图通过示例代码简化我的问题。我有一个生产者线程不断抽入数据,我试图批量批处理之间的时间延迟,以便UI有时间渲染它。但结果并不像预期的那样,产品和消费者似乎在同一个线索上。

我不希望批处理缓冲区在正在生成的线程上休眠。尝试SubscribeOn没有多大帮助。我在这里做错了什么,如何在生产者和消费者线程上打印不同的线程ID。

static void Main(string[] args)
{
    var stream = new ReplaySubject<int>();

    Task.Factory.StartNew(() =>
    {
        int seed = 1;
        while (true)
        {
            Console.WriteLine("Thread {0} Producing {1}",
                Thread.CurrentThread.ManagedThreadId, seed);

            stream.OnNext(seed);
            seed++;

            Thread.Sleep(TimeSpan.FromMilliseconds(500));
         }
    });

    stream.Buffer(5).Do(x =>
    {
        Console.WriteLine("Thread {0} sleeping to create time gap between batches",
            Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(TimeSpan.FromSeconds(2));
    })
    .SubscribeOn(NewThreadScheduler.Default).Subscribe(items =>
    {
        foreach (var item in items)
        {
            Console.WriteLine("Thread {0} Consuming {1}",
                Thread.CurrentThread.ManagedThreadId, item);
        }
    });
    Console.Read();
}

3 个答案:

答案 0 :(得分:2)

了解ObserveOnSubscribeOn之间的区别至关重要。有关这些内容的详细说明,请参阅 - ObserveOn and SubscribeOn - where the work is being done

另外,你绝对不想在你的Rx中使用Thread.Sleep。或任何地方。永远。 Do几乎同样邪恶,但Thead.Sleep几乎总是完全邪恶。缓冲区具有您想要使用的多个重载 - 这些重载包括基于时间的重载和接受计数限制时间限制的重载,当达到其中任何一个时返回缓冲区。基于时间的缓冲将在生产者和消费者之间引入必要的并发性 - 即,在与生产者的单独线程上向其订户提供缓冲区。

另请参阅这些问题和答案,这些问题和答案在保持消费者响应方面有很好的讨论(在WPF的背景下,但这些要点通常适用)。

上面的最后一个问题专门使用基于时间的缓冲区重载。正如我所说,在调用链中使用BufferObserveOn将允许您在生产者和消费者之间添加并发性。您仍然需要注意缓冲区的处理速度仍然足够快,以至于您不会在缓冲区用户上建立队列。

如果队列确实存在,您需要考虑应用背压,删除更新和/或混淆更新的方法。对于这里的深入讨论来说,这是一个过于广泛的大话题 - 但基本上你要么:

看看正确的缓冲是否有帮助,然后考虑在源头限制/混淆事件(UI只能显示如此多的信息) - 然后考虑更聪明的混淆,因为这可能变得非常复杂。 https://github.com/AdaptiveConsulting/ReactiveTrader是使用一些高级混淆技术的项目的一个很好的例子。

答案 1 :(得分:2)

虽然其他答案都是正确的,但我想确定您的实际问题可能是对Rx行为的误解。让生产者进入睡眠状态会阻止对OnNext的后续调用,看起来好像你假设Rx会同时自动调用OnNext,但事实上它并没有很好的理由。实际上,Rx有一份需要序列化通知的合同。

有关详细信息,请参阅Rx Design Guidelines中的§§4.2,6.7。

最终,您似乎正在尝试从Rxx实施BufferIntrospective运算符。此运算符允许您传入引入并发的调度程序,类似于ObserveOn,以在生成器和使用者之间创建并发边界。 BufferIntrospective是一种动态背压策略,可根据观察者不断变化的延迟推出异构大小的批次。当观察者正在处理当前批处理时,运算符将缓冲所有传入的并发通知。为了实现这一点,运算符利用OnNext是阻塞调用的事实(根据§4.2合同),因此该运算符应尽可能靠近查询边缘应用,通常是立即在致电Subscribe之前。

正如詹姆斯所描述的那样,你可以将其称为“智能缓冲”策略,或将其视为实施此类策略的基准;例如,我还定义了一个SampleIntrospective运算符,它除了每批中的最后一个通知外都会丢弃。

答案 2 :(得分:1)

ObserveOn可能就是你想要的。它需要SynchronizationContext作为参数,它应该是UI的SynchronizationContext。如果您不知道如何获取,请参阅Using SynchronizationContext for sending events back to the UI for WinForms or WPF