我试图通过示例代码简化我的问题。我有一个生产者线程不断抽入数据,我试图批量批处理之间的时间延迟,以便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();
}
答案 0 :(得分:2)
了解ObserveOn
和SubscribeOn
之间的区别至关重要。有关这些内容的详细说明,请参阅 - ObserveOn and SubscribeOn - where the work is being done。
另外,你绝对不想在你的Rx中使用Thread.Sleep
。或任何地方。永远。 Do
几乎同样邪恶,但Thead.Sleep
几乎总是完全邪恶。缓冲区具有您想要使用的多个重载 - 这些重载包括基于时间的重载和接受计数限制和时间限制的重载,当达到其中任何一个时返回缓冲区。基于时间的缓冲将在生产者和消费者之间引入必要的并发性 - 即,在与生产者的单独线程上向其订户提供缓冲区。
另请参阅这些问题和答案,这些问题和答案在保持消费者响应方面有很好的讨论(在WPF的背景下,但这些要点通常适用)。
上面的最后一个问题专门使用基于时间的缓冲区重载。正如我所说,在调用链中使用Buffer
或ObserveOn
将允许您在生产者和消费者之间添加并发性。您仍然需要注意缓冲区的处理速度仍然足够快,以至于您不会在缓冲区用户上建立队列。
如果队列确实存在,您需要考虑应用背压,删除更新和/或混淆更新的方法。对于这里的深入讨论来说,这是一个过于广泛的大话题 - 但基本上你要么:
Buffer
可以压缩事件,例如,只包含库存项目的最新价格等。您可以创建对运行敏感的运算符例如,OnNext
调用处理的时间。看看正确的缓冲是否有帮助,然后考虑在源头限制/混淆事件(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