События против Доходности - PullRequest
11 голосов
/ 04 августа 2010

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

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

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

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

Ответы [ 5 ]

7 голосов
/ 04 августа 2010

Это звучит как очень хороший вариант использования Reactive Extensions. В этом есть небольшая кривая обучения, но в двух словах, 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 секунда.

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

4 голосов
/ 04 августа 2010

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

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

2 голосов
/ 04 августа 2010

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

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

1 голос
/ 04 августа 2010

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

0 голосов
/ 04 августа 2010

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

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

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

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

...