События по сравнению с урожаем

У меня есть многопоточное приложение, которое порождает потоки для нескольких аппаратных инструментов. Каждый поток является в основном бесконечным циклом (в течение времени жизни приложения), который опрашивает аппаратные средства относительно новых данных и активирует событие (который передает данные), каждый раз, когда это собирает что-то новое. Существует единственный класс слушателя, который консолидирует все эти инструменты, выполняет некоторые вычисления и запускает новое событие с этим вычислением.

Однако я задаюсь вопросом, существует ли, с тех пор единственный слушатель, было бы лучше выставить IEnumerable<> метод от этих инструментов и использование a yield return возвратить данные, вместо того, чтобы запустить события.

Я хотел бы видеть, знает ли кто-либо о различиях в этих двух методах. В частности, я ищу лучшую надежность, лучшая способность приостановить/отменить операцию, лучше всего для поточной обработки целей, общей безопасности, и т.д.

Кроме того, со вторым методом это возможный все еще работать IEnumerable цикл на отдельном потоке? Многие из этих инструментов являются несколько Зависящими от ЦП, так удостоверяются, что каждый находится на различном потоке, жизненно важно.

11
задан drharris 4 August 2010 в 02:23
поделиться

5 ответов

Звучит как очень хороший вариант использования реактивных расширений. Для этого есть немного кривой обучения, но в двух словах IObservable - это двойник IEnumerable. Если IEnumerable требует, чтобы вы извлекли из него данные, IObservable передает свои значения наблюдателю. Практически каждый раз, когда вам нужно заблокировать в своем перечислителе, это хороший знак, что вы должны изменить шаблон и использовать модель push. События - это один из способов, но IObservable обладает гораздо большей гибкостью, поскольку он компонован и поддерживает потоки.

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

В приведенном выше примере DoSomethingWith (x) будет вызываться всякий раз, когда субъект (инструмент) создает DataEvent, который имеет соответствующее SomeProperty, и буферизует события в пакеты длительностью 1 секунду.

Есть еще много чего, что вы можете сделать, например, объединить события, созданные другими субъектами, или направить уведомления в поток пользовательского интерфейса и т. Д. К сожалению, документация в настоящее время довольно слабая, но есть хорошая информация в блоге Мэтью Подвизоцкого . (Хотя в его сообщениях почти исключительно упоминаются реактивные расширения для JavaScript, почти все это применимо и к реактивным расширениям для .NET.)

7
ответ дан 3 December 2019 в 08:28
поделиться

Это серьезный вызов, но я думаю, что в этом случае я бы придерживался модели событий, с основным решающим фактором, заключающимся в том, что будущие программисты обслуживания с меньшей вероятностью поймут концепцию доходности. Кроме того, yield означает, что код, обрабатывающий каждый аппаратный запрос, находится в том же потоке, что и код, генерирующий запросы на обработку. Это плохо, потому что это может означать, что ваше оборудование должно ждать кода потребителя.

Говоря о потребителях, еще один вариант - это очередь производителя / потребителя. Все ваши инструменты могут помещаться в одну и ту же очередь, и ваш единственный слушатель может затем выходить из нее и делать все, что оттуда.

4
ответ дан 3 December 2019 в 08:28
поделиться

Это довольно фундаментальное различие, push vs pull. Модель pull (yield) сложнее реализовать с точки зрения инструментального интерфейса. Потому что вам придется хранить данные до тех пор, пока клиентский код не будет готов к извлечению. Когда вы толкаете, клиент может хранить или не хранить, как он считает нужным.

Но большинство практических реализаций в многопоточных сценариях должны иметь дело с накладными расходами на неизбежное переключение контекста потока, которое требуется для представления данных. И это часто делается с помощью pull, используя потокобезопасную ограниченную очередь.

2
ответ дан 3 December 2019 в 08:28
поделиться

Я не думаю, что есть большая разница в производительности между подходом event и yield . Доходность вычисляется лениво, поэтому он оставляет возможность сигнализировать производящим потокам об остановке. Если ваш код тщательно задокументирован, то обслуживание тоже должно быть тщательным.

Я предпочитаю третий вариант - использовать метод обратного вызова вместо события (даже если оба связаны с делегатами). Ваши производители вызывают обратный вызов каждый раз, когда у них есть данные. Обратные вызовы могут возвращать значения, поэтому ваш потребитель может сигнализировать производителям, чтобы они остановились или продолжили каждый раз, когда они проверяют данные.

Этот подход может дать вам возможность оптимизировать производительность, если у вас большой объем данных. В обратном вызове вы фиксируете нейтральный объект и добавляете входящие данные в коллекцию. Среда выполнения внутренне использует готовую очередь для объекта блокировки, поэтому она может служить вашей точкой очереди.

Это позволяет вам выбрать коллекцию, такую ​​как List с предопределенной емкостью, то есть O (1) для добавления. Вы также можете выполнить двойную буферизацию своего потребителя, при этом обратный вызов будет добавляться в «левый» буфер, а вы объединяетесь из «правого» и т. Д. Это сводит к минимуму количество блокировок производителя и связанных пропущенных данных, что удобно для пакетных данных.Вы также можете легко измерить высшие отметки и скорость обработки, изменив количество потоков.

0
ответ дан 3 December 2019 в 08:28
поделиться

Стивен Туб блог о блокирующей очереди, которая реализует IEnumerable как бесконечный цикл с использованием ключевого слова yield . Ваши рабочие потоки могут ставить новые точки данных в очередь по мере их появления, а вычислительный поток может выводить их из очереди, используя цикл foreach с семантикой блокировки.

1
ответ дан 3 December 2019 в 08:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: