Почему предметы не рекомендуются in.NET реактивные расширения?


в настоящее время я приступаю к работе с платформой реактивных расширений для .NET, и я работаю над различными ресурсами введения, которые я нашел (в основном http://www.introtorx.com)

наше приложение включает в себя ряд аппаратных интерфейсов, которые обнаруживают сетевые кадры, это будут мои IObservables, затем у меня есть множество компонентов, которые будут потреблять эти кадры или выполнять какой-то способ преобразования данных и создавать новый тип рамка. Там также будут другие компоненты, которые должны отображать каждый n-й кадр, например. Я убежден, что Rx будет полезен для нашего приложения, однако я борюсь с деталями реализации для интерфейса IObserver.

большинство (если не все) ресурсов, которые я читал, сказали, что я не должен реализовывать интерфейс IObservable самостоятельно, но использовать одну из предоставленных функций или классов. Из моих исследований кажется, что создание Subject<IBaseFrame> будет предоставьте мне то, что мне нужно, у меня будет мой единственный поток, который считывает данные из аппаратного интерфейса, а затем вызывает функцию OnNext my Subject<IBaseFrame> экземпляра. Затем различные компоненты IObserver получат свои уведомления от этого субъекта.

моя путаница исходит из советов, приведенных в приложении в этом уроке где он говорит:

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

мое приложение довольно критично для производительности, я, очевидно, собираюсь проверить производительность использования шаблонов Rx, прежде чем он перейдет в производственный код; однако я беспокоюсь, что я делаю что-то, что противоречит духу RX framework, используя класс Subject и что будущая версия фреймворка будет вредить производительности.

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

любой совет был бы весьма признателен.

5 92

5 ответов:

ОК, Если мы игнорируем мои догматические пути и игнорируем "субъекты хороши / плохи" все вместе. Давайте посмотрим на проблемное пространство.

бьюсь об заклад, у вас либо есть 1 из 2 стилей системы, которые вам нужно неблагодарить.

  1. система вызывает событие или обратный вызов при поступлении сообщения
  2. вам нужно опросить систему, чтобы увидеть, если есть какие-либо сообщения для обработки

для варианта 1, легко, мы просто обернуть его с соответствующим методом FromEvent и мы все готово. В паб!

для варианта 2 Теперь нам нужно рассмотреть, как мы опрашиваем это и как это сделать эффективно. Также, Когда мы получаем значение, как мы его публикуем?

Я бы предположил, что вам понадобится выделенный поток для опроса. Вы не хотели бы, чтобы какой-то другой кодер забивал ThreadPool/TaskPool и оставлял вас в ситуации голодания ThreadPool. В качестве альтернативы вы не хотите хлопот переключения контекста (я думаю). Поэтому предположим, что у нас есть своя нить, мы будем вероятно, есть какой-то цикл While/Sleep, в котором мы сидим, чтобы опросить. Когда проверка находит некоторые сообщения, мы их публикуем. Ну все это звучит идеально подходит для наблюдаемых.Создавать. Теперь мы, вероятно, не можем использовать цикл While, поскольку это не позволит нам когда-либо вернуть одноразовый, чтобы разрешить отмену. К счастью, вы прочитали всю книгу, так что подкованы с рекурсивным планированием!

Я думаю, что-то вроде этого может сработать. #NotTested

public class MessageListener
{
    private readonly IObservable<IMessage> _messages;
    private readonly IScheduler _scheduler;

    public MessageListener()
    {
        _scheduler = new EventLoopScheduler();

        var messages = ListenToMessages()
                                    .SubscribeOn(_scheduler)
                                    .Publish();

        _messages = messages;
        messages.Connect();
    }

    public IObservable<IMessage> Messages
    {
        get {return _messages;}
    }

    private IObservable<IMessage> ListenToMessages()
    {
        return Observable.Create<IMessage>(o=>
        {
                return _scheduler.Schedule(recurse=>
                {
                    try
                    {           
                        var messages = GetMessages();
                        foreach (var msg in messages)
                        {
                            o.OnNext(msg);
                        }   
                        recurse();
                    }
                    catch (Exception ex)
                    {
                        o.OnError(ex);
                    }                   
                });
        });
    }

    private IEnumerable<IMessage> GetMessages()
    {
         //Do some work here that gets messages from a queue, 
         // file system, database or other system that cant push 
         // new data at us.
         // 
         //This may return an empty result when no new data is found.
    }
}

причина мне очень не нравится Субъекты, это, как правило, случай разработчика, не имеющего четкого дизайна по проблеме. Взломайте тему, ткните ее здесь там и везде, а затем пусть бедная поддержка dev догадается, что WTF происходит. Когда вы используете создавать/генерировать и т. д. методы локализации воздействия на последовательность. Вы можете увидеть все это в одном методе, и вы знаете, что никто другой не бросает неприятный побочный эффект. Если я вижу предметные поля, мне теперь нужно искать все места в классе, в котором он находится используемый. Если какой-то Мфер выставляет один публично, то все ставки выключены, кто знает, как эта последовательность используется! Асинхронность / параллелизм/Rx-это сложно. Вам не нужно усложнять ситуацию, позволяя побочным эффектам и программированию причинности еще больше вращать вашу голову.

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

цитата Дейв Секстон (из Rxx)

"субъекты являются статусными компонентами Rx. Они полезны для Когда вам нужно создать событие, подобное наблюдаемому как поле или локальное переменная."

я склонен использовать их в качестве точки входа в Rx. Поэтому, если у меня есть какой-то код, который должен сказать "что-то случилось" (как у вас), я бы использовал Subject и звонок OnNext. Тогда выставьте это как IObservable для других, чтобы подписаться (вы можете использовать AsObservable() по вашей теме, чтобы убедиться, что никто не может бросить в тему и испортить вещи).

вы также можете достичь этого с помощью события .NET и использовать FromEventPattern, но если я только собираюсь превратить событие в Ан IObservable в любом случае, я не вижу смысла в том, чтобы иметь событие вместо Subject (Что может означать, что я что-то упускаю)

однако, то, что вы должны избегать довольно сильно подписывается на IObservable С Subject, т. е. не пройти Subject на IObservable.Subscribe метод.

часто, когда вы управляете предметом, вы на самом деле просто переопределяете функции уже в Rx и, вероятно, не так надежны, просты и расширяемы.

когда вы пытаетесь адаптировать некоторый асинхронный поток данных в Rx (или создать асинхронный поток данных из того, который в настоящее время не асинхронен), наиболее распространенными случаями обычно являются:

  • источником данных является событие: как говорит ли, это самый простой случай: использование С вечера и направляйтесь в паб.

  • источник данных из синхронной операции, и вы хотите, чтобы опрошенные обновления, (например, вызов веб-службы или базы данных): в этом случае вы можете использовать предложенный ли подход, или для простых случаев, вы можете использовать что-то вроде Observable.Interval.Select(_ => <db fetch>). Вы можете использовать DistinctUntilChanged () для предотвращения публикации обновлений, когда в исходных данных ничего не изменилось.

  • источник данных-это некоторые вид асинхронного API, который вызывает ваш обратный вызов: в этом случае используйте Observable.Создайте, чтобы подключить обратный вызов для вызова OnNext/OnError/OnComplete на наблюдателе.

  • источник данных-это вызов, который блокируется до появления новых данных (например, некоторые синхронные операции чтения сокета): в этом случае вы можете использовать Observable.Создайте, чтобы обернуть императивный код, который считывает из сокета и публикует наблюдателю.Наследующей при чтении данных. Май этого года будьте похожи на то, что вы делаете с предметом.

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

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

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

цитируемый текст блока в значительной степени объясняет, почему вы не должны использовать Subject<T>, но проще говоря, вы объединяете функции наблюдателя и наблюдаемого, одновременно вводя какое-то промежуточное состояние (инкапсулируете ли вы или расширяете).

это где вы столкнетесь с проблемами; эти обязанности должны быть отдельными и отличными друг от друга.

что сказал, в вашем конкретные случае, я бы рекомендовал что вы разбиваете свои проблемы на более мелкие части.

во-первых, у вас есть свой поток, который горячий, и всегда контролирует аппаратное обеспечение для сигналов для получения уведомлений. Как бы вы сделали это обычно? событий. Так что давайте начнем с этого.

давайте определим EventArgs что ваше мероприятие будет огонь.

// The event args that has the information.
public class BaseFrameEventArgs : EventArgs
{
    public BaseFrameEventArgs(IBaseFrame baseFrame)
    {
        // Validate parameters.
        if (baseFrame == null) throw new ArgumentNullException("IBaseFrame");

        // Set values.
        BaseFrame = baseFrame;
    }

    // Poor man's immutability.
    public IBaseFrame BaseFrame { get; private set; }
}

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

public class BaseFrameMonitor
{
    // You want to make this access thread safe
    public event EventHandler<BaseFrameEventArgs> HardwareEvent;

    public BaseFrameMonitor()
    {
        // Create/subscribe to your thread that
        // drains hardware signals.
    }
}

Итак, у вас есть класс, который предоставляет событие. Наблюдаемые хорошо работают с событиями. Настолько, что есть первоклассная поддержка для преобразования потоков событий (подумайте о потоке событий как о нескольких запусках события) в IObservable<T> реализации, если вы следуете стандартному событию узор, через статический FromEventPattern метод на Observable класс.

С источником ваших событий, и FromEventPattern метод, мы можем создать IObservable<EventPattern<BaseFrameEventArgs>> легко (the EventPattern<TEventArgs> класс воплощает то, что вы увидите в событии .NET, в частности, экземпляр, производный от EventArgs и объект, представляющий отправителя), например:

// The event source.
// Or you might not need this if your class is static and exposes
// the event as a static event.
var source = new BaseFrameMonitor();

// Create the observable.  It's going to be hot
// as the events are hot.
IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
    FromEventPattern<BaseFrameEventArgs>(
        h => source.HardwareEvent += h,
        h => source.HardwareEvent -= h);

конечно, вы хотите IObservable<IBaseFrame>, но это легко, с помощью Select метод расширения на Observable класс для создания проекции (так же, как и в LINQ, и мы можем обернуть все это в простой в использовании метод):

public IObservable<IBaseFrame> CreateHardwareObservable()
{
    // The event source.
    // Or you might not need this if your class is static and exposes
    // the event as a static event.
    var source = new BaseFrameMonitor();

    // Create the observable.  It's going to be hot
    // as the events are hot.
    IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
        FromEventPattern<BaseFrameEventArgs>(
            h => source.HardwareEvent += h,
            h => source.HardwareEvent -= h);

    // Return the observable, but projected.
    return observable.Select(i => i.EventArgs.BaseFrame);
}

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

Если у вас есть обычное свойство с общедоступным набором доступа, и вы хотите уведомить об изменениях, нет ничего против замены его объектом BehaviorSubject. INPC или дополнительные другие события просто нет это чисто, и это лично меня утомляет. Для этой цели вы можете и должны использовать BehaviorSubjects в качестве общедоступных свойств вместо обычных свойств и ditch INPC или других событий.

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

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