事件与产量

发布于 2024-09-13 03:07:10 字数 402 浏览 8 评论 0 原文

我有一个多线程应用程序,它为多个硬件仪器生成线程。每个线程基本上都是一个无限循环(在应用程序的生命周期内),它轮询硬件以获取新数据,并在每次收集新数据时激活一个事件(传递数据)。有一个监听器类可以整合所有这些工具,执行一些计算,并通过此计算触发一个新事件。

但是,我想知道,由于只有一个侦听器,因此最好在这些仪器上公开 IEnumerable<> 方法,并使用 yield return返回数据,而不是触发事件。

我想看看是否有人知道这两种方法的区别。特别是,我正在寻找最好的可靠性、最好的暂停/取消操作的能力、最好的线程目的、一般安全性等。

此外,使用第二种方法是否仍然可以运行 IEnumerable 在单独的线程上循环?其中许多工具都受到 CPU 的限制,因此确保每个工具位于不同的线程上至关重要。

I have a multithreaded application that spawns threads for several hardware instruments. Each thread is basically an infinite loop (for the lifetime of the application) that polls the hardware for new data, and activates an event (which passes the data) each time it collects something new. There is a single listener class that consolidates all these instruments, performs some calculations, and fires a new event with this calculation.

However, I'm wondering if, since there is a single listener, it would be better to expose an IEnumerable<> method off these instruments, and use a yield return to return the data, instead of firing events.

I'd like to see if anybody knows of differences in these two methods. In particular, I'm looking for the best reliability, best ability to pause/cancel operation, best for threading purposes, general safety, etc.

Also, with the second method is it possible to still run the IEnumerable loop on a separate thread? Many of these instruments are somewhat CPU-bound, so ensuring each one is on a different thread is vital.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

陪你搞怪i 2024-09-20 03:07:10

这听起来像是反应式扩展的一个非常好的用例。虽然有一点学习曲线,但简而言之,IObservable 是 IEnumerable 的对偶。 IEnumerable 要求您从中提取值,而 IObservable 将其值推送给观察者。几乎任何时候您需要阻止枚举器时,这是一个好兆头,您应该扭转该模式并使用推送模型。事件是一种方法,但 IObservable 具有更大的灵活性,因为它是可组合的并且是线程感知的。

instrument.DataEvents
          .Where(x => x.SomeProperty == something)
          .BufferWithTime( TimeSpan.FromSeconds(1) )
          .Subscribe( x => DoSomethingWith(x) );

在上面的示例中,每当主体(仪器)产生具有匹配的 SomeProperty 的 DataEvent 时,都会调用 DoSomethingWith(x) ,并将事件缓冲为持续时间为 1 秒的批次。

您还可以做更多的事情,例如合并其他主题生成的事件或将通知定向到 UI 线程等。不幸的是,文档目前相当薄弱,但在 Matthew Podwysocki 的博客。 (虽然他的帖子几乎只提到 JavaScript 的响应式扩展,但它几乎也适用于 .NET 的响应式扩展。)

This sounds like a very good use case for the Reactive Extensions. There's a little bit of a learning curve to it but in a nutshell, IObservable is the dual of IEnumerable. Where IEnumerable requires you to pull from it, IObservable pushes its values to the observer. Pretty much any time you need to block in your enumerator, it's a good sign you should reverse the pattern and use a push model. Events are one way to go but IObservable has much more flexibility since it's composable and thread-aware.

instrument.DataEvents
          .Where(x => x.SomeProperty == something)
          .BufferWithTime( TimeSpan.FromSeconds(1) )
          .Subscribe( x => DoSomethingWith(x) );

In the above example, DoSomethingWith(x) will be called whenever the subject (instrument) produces a DataEvent that has a matching SomeProperty and it buffers the events into batches of 1 second duration.

There's plenty more you could do such as merging in the events produced by other subjects or directing the notifications onto the UI thread, etc. Unfortunately documentation is currently pretty weak but there's some good information on Matthew Podwysocki's blog. (Although his posts almost exclusively mention Reactive Extensions for JavaScript, it's pretty much all applicable to Reactive Extensions for .NET as well.)

捎一片雪花 2024-09-20 03:07:10

这是一次千钧一发的机会,但我认为在这种情况下我会坚持使用事件模型,主要决策者认为未来的维护程序员不太可能理解产量概念。此外,产出意味着处理每个硬件请求的代码与生成处理请求的代码位于同一线程中。这很糟糕,因为这可能意味着您的硬件必须等待消费者代码。

说到消费者,另一种选择是生产者/消费者队列。您的乐器可以全部推入同一个队列,然后您的单个听众可以从队列中弹出,从那里做任何事情。

It's a close call, but I think I'd stick to the event model in this case, with the main decider behing that future maintenance programmers are less likely to understand the yield concept. Also, yield means the code processing each hardware request is in the same thread as the code generating the requests for processing. That's bad, because it could mean your hardware has to wait on the consumer code.

And speaking of consumers, another option is a producer/consumer queue. Your instruments can all push into the same queue and your single listener can then pop from it do whatever from there.

爱情眠于流年 2024-09-20 03:07:10

推与拉有一个非常根本的区别。从仪器界面视图来看,拉动模型(产量)是更难实现的模型。因为您必须存储数据,直到客户端代码准备好提取。当您推送时,客户端可能会或可能不会存储,因为它认为有必要。

但多线程场景中的大多数实际实现都需要处理呈现数据所需的不可避免的线程上下文切换的开销。这通常是通过拉动、使用线程安全的有界队列来完成的。

There's a pretty fundamental difference, push vs pull. The pull model (yield) being the harder one to implement from the instrument interface view. Because you'll have to store data until the client code is ready to pull. When you push, the client may or may not store, as it deems necessary.

But most practical implementations in multi-threading scenarios need to deal with the overhead in the inevitable thread context switch that's required to present data. And that's often done with pull, using a thread-safe bounded queue.

纵情客 2024-09-20 03:07:10

Stephen Toub 关于实现 < 的阻塞队列的博客 code>IEnumerable 作为使用 yield 关键字的无限循环。您的工作线程可以在新数据点出现时将其加入队列,并且计算线程可以使用具有阻塞语义的 foreach 循环将它们出队。

Stephen Toub blogs about a blocking queue which implements IEnumerable as an infinite loop using the yield keyword. Your worker threads could enqueue new data points as they appear and the calculation thread could dequeue them using a foreach loop with blocking semantics.

〃安静 2024-09-20 03:07:10

我认为事件和 yield 方法之间在性能方面没有太大差异。 Yield 是惰性计算的,因此它留下了一个机会来通知生产线程停止。如果你的代码经过深思熟虑地记录下来,那么维护也应该是一件轻松的事情。

我更喜欢第三种选择,即使用回调方法而不是事件(即使两者都涉及委托)。您的生产者每次获得数据时都会调用回调。回调可以返回值,因此您的消费者可以在每次签入数据时向生产者发出停止或继续的信号。

如果您有大量数据,此方法可以为您提供优化性能的地方。在回调中,您锁定一个中性对象并将传入数据附加到集合中。运行时在内部使用锁对象上的就绪队列,因此这可以作为您的排队点。

这使您可以选择一个集合,例如具有预定义容量的 List,即追加容量为 O(1)。您还可以对消费者进行双缓冲,将回调附加到“左”缓冲区,同时从“右”缓冲区进行合并,依此类推。这最大限度地减少了生产者阻塞和相关丢失数据的数量,这对于突发数据来说很方便。当您改变线程数量时,您还可以轻松测量高水位线和处理速率。

I don't think there's much difference performance-wise between the event and yield approach. Yield is lazy evaluated, so it leaves an opportunity to signal the producing threads to stop. If your code is thoughtfully documented then maintenance ought to be a wash, too.

My preference is a third option, to use a callback method instead of an event (even though both involve delegates). Your producers invoke the callback each time they have data. Callbacks can return values, so your consumer can signal producers to stop or continue each time they check in with data.

This approach can give you places to optimize performance if you have a high volume of data. In your callback you lock on a neutral object and append incoming data to a collection. The runtime internally uses an ready queue on the lock object, so this can serve as your queuing point.

This lets you choose a collection, such as a List<T> with predefined capacity, that is O(1) for appending. You can also double-buffer your consumer, with your callback appending to the "left" buffer while you consolidate from the "right" one, and so forth. This minimizes the amount of producer blocking and associated missed data, which is handy for bursty data. You can also readily measure high-water marks and processing rates as you vary the number of threads.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文