В настоящее время я занимаюсь фреймворком Reactive Extensions для .NET и прорабатываю различные вводные ресурсы, которые я нашел (в основном http://www.introtorx.com )
Наше приложение включает в себя несколько аппаратных интерфейсов, которые обнаруживают сетевые фреймы, это будут мои IObservables, а затем у меня будет множество компонентов, которые будут использовать эти фреймы или выполнять какое-либо преобразование данных и создавать новый тип фрейма. Также будут другие компоненты, которые, например, должны отображать каждый n-й кадр. Я убежден, что Rx будет полезен для нашего приложения, однако я борюсь с деталями реализации интерфейса IObserver.
В большинстве (если не во всех) ресурсов, которые я читал, говорится, что я не должен сам реализовывать интерфейс IObservable, а должен использовать одну из предоставленных функций или классов. Из моих исследований видно, что создание a Subject<IBaseFrame>
предоставит мне то, что мне нужно, у меня будет мой единственный поток, который считывает данные с аппаратного интерфейса, а затем вызывает функцию OnNext моего Subject<IBaseFrame>
экземпляра. Затем различные компоненты IObserver будут получать свои уведомления от этого субъекта.
Мое замешательство вызвано советом, приведенным в приложении к этому руководству, где говорится:
Избегайте использования тематических типов. Rx - это, по сути, парадигма функционального программирования. Использование субъектов означает, что теперь мы управляем состоянием, которое потенциально может изменяться. Правильно справиться и с изменяющимся состоянием, и с асинхронным программированием одновременно очень сложно. Более того, многие операторы (методы расширения) были тщательно написаны, чтобы обеспечить правильное и постоянное время жизни подписок и последовательностей; когда вы вводите предметы, вы можете сломать это. В будущих выпусках также может наблюдаться значительное снижение производительности, если вы явно используете темы.
Мое приложение весьма критично к производительности, я, очевидно, собираюсь протестировать производительность использования шаблонов Rx, прежде чем оно попадет в производственный код; однако меня беспокоит, что я делаю что-то, что противоречит духу структуры Rx, используя класс Subject, и что будущая версия инфраструктуры может снизить производительность.
Есть ли лучший способ делать то, что я хочу? Поток аппаратного опроса будет работать постоянно, независимо от того, присутствуют ли какие-либо наблюдатели или нет (в противном случае будет выполнено резервное копирование HW-буфера), так что это очень горячая последовательность. Затем мне нужно передать полученные кадры нескольким наблюдателям.
Любой совет будет очень признателен.
источник
Ответы:
Хорошо, если мы проигнорируем мои догматические взгляды и проигнорируем все вместе «предметы хорошие / плохие». Давайте посмотрим на проблемное пространство.
Бьюсь об заклад, у вас либо есть 1 из 2 стилей системы, к которым вам нужно снискать расположение.
Для варианта 1, просто, мы просто оборачиваем его соответствующим методом FromEvent, и все готово. В паб!
Что касается варианта 2, то теперь нам нужно подумать о том, как мы проводим опрос и как это сделать эффективно. Также, когда мы получаем значение, как мы его публикуем?
Я предполагаю, что вам понадобится выделенная ветка для опроса. Вы бы не хотели, чтобы какой-то другой программист забил ThreadPool / TaskPool и оставил вас в ситуации голодания ThreadPool. В качестве альтернативы вы не хотите хлопот с переключением контекста (я думаю). Итак, предположим, что у нас есть собственный поток, у нас, вероятно, будет какой-то цикл While / Sleep, который мы будем опрашивать. Когда проверка обнаруживает сообщения, мы их публикуем. Что ж, все это звучит идеально для Observable.Create. Теперь мы, вероятно, не можем использовать цикл While, так как он не позволит нам когда-либо вернуть Disposable, чтобы разрешить отмену. К счастью, вы прочитали всю книгу, так что разбираетесь в рекурсивном планировании!
Думаю, что-то подобное могло сработать. #Не проверено
Причина, по которой мне действительно не нравятся Subjects, заключается в том, что обычно разработчик не имеет четкого представления о проблеме. Взломайте тему, ткните ее здесь, там и везде, а затем пусть бедный разработчик поддержки догадывается, что происходит WTF. Когда вы используете методы Create / Generate и т. Д., Вы локализуете эффекты в последовательности. Вы можете увидеть все это в одном методе, и вы знаете, что никто другой не вызывает неприятных побочных эффектов. Если я вижу поля темы, мне нужно искать все места в классе, в которых они используются. Если какой-то MFer выставляет один публично, то все ставки отключены, кто знает, как эта последовательность используется! Async / Concurrency / Rx - это сложно. Вам не нужно усложнять задачу, позволяя побочным эффектам и программированию причинно-следственной связи еще больше вскружить вам голову.
источник
В целом вам следует избегать использования
Subject
, однако для того, что вы здесь делаете, я думаю, они работают достаточно хорошо. Я задал аналогичный вопрос, когда наткнулся на сообщение «Избегайте тем» в руководствах по Rx.Процитирую Дэйва Секстона (из Rxx)
Я предпочитаю использовать их как точку входа в Rx. Поэтому, если у меня есть код, который должен сказать «что-то произошло» (как у вас), я бы использовал
Subject
и callOnNext
. Затем представьте это как возможностьIObservable
для подписки другим пользователям (вы можете использовать этоAsObservable()
в своей теме, чтобы убедиться, что никто не может применить к Subject и все испортить).Вы также можете добиться этого с помощью события .NET и использовать его
FromEventPattern
, но если я всеIObservable
равно собираюсь превратить событие в событие , я не вижу преимущества наличия события вместоSubject
(что может означать, что мне не хватает что-то здесь)Однако, чего вам следует избегать, так это подписки на
IObservable
aSubject
, т.е. не передавать aSubject
вIObservable.Subscribe
метод.источник
Часто, когда вы управляете предметом, вы на самом деле просто переопределяете функции, уже существующие в Rx, и, вероятно, не таким надежным, простым и расширяемым способом.
Когда вы пытаетесь адаптировать некоторый асинхронный поток данных к Rx (или создать асинхронный поток данных из потока, который в настоящее время не является асинхронным), наиболее распространенными случаями обычно являются:
Источником данных является событие : как говорит Ли, это самый простой случай: используйте FromEvent и отправляйтесь в паб.
Источником данных является синхронная операция, и вам нужны опрашиваемые обновления (например, веб-сервис или вызов базы данных): в этом случае вы можете использовать подход, предложенный Ли, или для простых случаев вы можете использовать что-то вроде
Observable.Interval.Select(_ => <db fetch>)
. Вы можете использовать DistinctUntilChanged (), чтобы предотвратить публикацию обновлений, когда в исходных данных ничего не изменилось.Источником данных является своего рода асинхронный api, который вызывает ваш обратный вызов : в этом случае используйте Observable.Create, чтобы подключить ваш обратный вызов для вызова OnNext / OnError / OnComplete для наблюдателя.
Источником данных является вызов, который блокируется до тех пор, пока не станут доступны новые данные (например, некоторые синхронные операции чтения из сокета): в этом случае вы можете использовать Observable.Create, чтобы обернуть императивный код, который читает из сокета и публикует в Observer.OnNext при чтении данных. Это может быть похоже на то, что вы делаете с темой.
Использование Observable.Create по сравнению с созданием класса, который управляет Subject, довольно эквивалентно использованию ключевого слова yield по сравнению с созданием всего класса, который реализует IEnumerator. Конечно, вы можете написать IEnumerator, чтобы он был таким же чистым и хорошим гражданином, как код yield, но какой из них лучше инкапсулирован и выглядит более аккуратным? То же самое верно для Observable.Create и управления Subject.
Observable.Create дает вам чистый шаблон для ленивой установки и чистого разрыва. Как этого добиться с помощью класса, обертывающего тему? Вам нужен какой-то метод Start ... как узнать, когда его вызвать? Или вы просто всегда запускаете его, даже когда его никто не слушает? И когда вы закончите, как вы заставите его прекратить чтение из сокета / опрос базы данных и т. Д.? У вас должен быть какой-то метод Stop, и у вас все еще должен быть доступ не только к IObservable, на который вы подписаны, но и к классу, который изначально создал Subject.
С Observable.Create все это собрано в одном месте. Тело Observable.Create не запускается, пока кто-то не подпишется, поэтому, если никто не подписывается, вы никогда не используете свой ресурс. И Observable.Create возвращает Disposable, который может полностью отключить ваш ресурс / обратные вызовы и т.д. - это вызывается, когда Observer отменяет подписку. Время жизни ресурсов, которые вы используете для создания Observable, аккуратно привязано к времени жизни самого Observable.
источник
Цитируемый текст блока в значительной степени объясняет, почему вы не должны использовать
Subject<T>
, но, говоря проще, вы комбинируете функции наблюдателя и наблюдаемого, вводя какое-то состояние между ними (независимо от того, инкапсулируете ли вы или расширяете).Здесь вы столкнетесь с проблемой; эти обязанности должны быть отдельными и отличными друг от друга.
Тем не менее, в вашем конкретном случае я бы рекомендовал вам разбить ваши проблемы на более мелкие части.
Во-первых, у вас есть активный поток, и вы всегда отслеживаете оборудование для сигналов, для которых нужно создавать уведомления. Как бы вы это сделали обычно? События . Итак, начнем с этого.
Определим,
EventArgs
что будет срабатывать ваше событие.Теперь класс, который запускает событие. Обратите внимание, что это может быть статический класс (так как вы всегда есть поток , работающий мониторинг аппаратного буфера), или то , что вы звоните по требованию , который подписывается на что . Вам нужно будет изменить это соответствующим образом.
Итак, теперь у вас есть класс, который предоставляет событие. Наблюдаемые хорошо работают с событиями. Настолько , что есть поддержка первого класса для преобразования потоков событий (думать о потоке событий как несколько включений события) в
IObservable<T>
реализацию , если вы будете следовать стандартной схеме событий, через статическийFromEventPattern
метод наObservable
классе .Имея источник ваших событий и
FromEventPattern
метод, мы можемIObservable<EventPattern<BaseFrameEventArgs>>
легко создать (EventPattern<TEventArgs>
класс воплощает то, что вы видите в событии .NET, в частности, экземпляр, производный отEventArgs
и объект, представляющий отправителя), например:Конечно, вам нужен
IObservable<IBaseFrame>
, но это легко, используяSelect
метод расширения вObservable
классе для создания проекции (точно так же, как и в LINQ, и мы можем обернуть все это простым в использовании методом):источник
IObservable<T>
отсутствие информации о том, как вы» дается текущая сигнализация с этой информацией.Плохо обобщать, что субъекты не подходят для публичного интерфейса. Хотя это, безусловно, правда, что подход реактивного программирования должен выглядеть не так, это определенно хороший вариант улучшения / рефакторинга для вашего классического кода.
Если у вас есть обычное свойство с общедоступным набором средств доступа и вы хотите уведомлять об изменениях, ничто не говорит против замены его на BehaviorSubject. INPC или другие другие мероприятия не так уж чисты, и это меня утомляет. Для этого вы можете и должны использовать BehaviorSubjects в качестве общедоступных свойств вместо обычных свойств и отказаться от INPC или других событий.
Кроме того, Subject-interface делает пользователей вашего интерфейса более осведомленными о функциональности ваших свойств и с большей вероятностью подписывается, а не просто получает значение.
Лучше всего использовать, если вы хотите, чтобы другие слушали / подписывались на изменения свойства.
источник