Подтвердить что ты не робот

События против дохода

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

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

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

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

4b9b3361

Ответ 1

Это звучит как очень полезный вариант для 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.)

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

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

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