Был ли .NET IObserver <T> предназначен для подписки на несколько IObserable?


9

В .NET есть интерфейсы IObservable и IObserver (также здесь и здесь ). Интересно, что конкретная реализация IObserver не содержит прямой ссылки на IObservable. Он не знает, на кого он подписан. Это может только вызвать отписчика. «Пожалуйста, потяните за булавку, чтобы отписаться».

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

Две вещи мне не совсем понятны.

  1. Обеспечивает ли внутренний класс Unsubscriber поведение подписки и забывания? Кто (и когда именно) звонит IDisposable.Dispose()Unsubscriber? Сборщик мусора (GC) не является детерминированным.
    [Отказ от ответственности: в целом, я провел больше времени с C и C ++, чем с C #.]
  2. Что должно произойти, если я хочу подписать наблюдателя K на наблюдаемый L1, а наблюдатель уже подписан на какой-то другой наблюдаемый L2?

    K.Subscribe(L1);
    K.Subscribe(L2);
    K.Unsubscribe();
    L1.PublishObservation(1003);
    L2.PublishObservation(1004);
    

    Когда я запускал этот тестовый код на примере MSDN, наблюдатель оставался подписанным на L1. Это было бы особенным в реальном развитии. Потенциально, есть 3 пути для улучшения этого:

    • Если у наблюдателя уже есть экземпляр отписчика (т. Е. Он уже подписан), то он тихо отписывается от исходного провайдера перед подпиской на нового. Этот подход скрывает тот факт, что он больше не подписан на первоначального поставщика, что может стать неожиданностью позже.
    • Если у наблюдателя уже есть экземпляр отписчика, он вызывает исключение. Правильный код вызова должен явно отписаться от наблюдателя.
    • Обозреватель подписывается на несколько поставщиков. Это наиболее интригующий вариант, но может ли он быть реализован с помощью IObservable и IObserver? Посмотрим. Наблюдатель может вести список неподписанных объектов: по одному для каждого источника. К сожалению, IObserver.OnComplete()не предоставляет ссылку обратно поставщику, который его отправил. Таким образом, реализация IObserver с несколькими провайдерами не сможет определить, от кого отписаться.
  3. Был ли IObserver .NET предназначен для подписки на несколько IObserable?
    Требуется ли из определения шаблона наблюдателя в учебнике, что один наблюдатель должен иметь возможность подписаться на несколько поставщиков? Или это необязательно и зависит от реализации?

Ответы:


5

Два интерфейса на самом деле являются частью Reactive Extensions (Rx для краткости), вы должны использовать эту библиотеку почти всегда, когда вы хотите их использовать.

Технически интерфейсы находятся в mscrolib, а не в любой из сборок Rx. Я думаю, что это должно облегчить взаимодействие: таким образом, библиотеки, такие как TPL Dataflow, могут предоставлять члены, которые работают с этими интерфейсами , без фактической ссылки на Rx.

Если вы используете Rx в Subjectкачестве своей реализации IObservable, Subscribeвернет, IDisposableчто может быть использовано для отписки:

var observable = new Subject<int>();

var unsubscriber =
    observable.Subscribe(Observer.Create<int>(i => Console.WriteLine("1: {0}", i)));
observable.Subscribe(Observer.Create<int>(i => Console.WriteLine("2: {0}", i)));

unsubscriber.Dispose();

observable.OnNext(1003);
observable.OnNext(1004);

5

Просто чтобы прояснить некоторые вещи, которые хорошо документированы в официальных Руководствах по проектированию Rx и подробно на моем веб-сайте IntroToRx.com :

  • Вы не полагаетесь на GC для очистки ваших подписок. Покрыто в деталяхЗдесь
  • Там нет Unsubscribeметода. Вы подписываетесь на наблюдаемую последовательность и получаете подписку . Затем вы можете избавиться от этой подписки, указав, что вы больше не хотите, чтобы ваши обратные вызовы вызывались.
  • Наблюдаемая последовательность не может быть завершена более одного раза (см. Раздел 4 Руководства по проектированию Rx).
  • Есть множество способов потреблять несколько наблюдаемых последовательностей. На Reactivex.io и снова на IntroToRx имеется множество информации об этом .

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

Вместо

K.Subscribe(L1);
K.Subscribe(L2);
K.Unsubscribe();
L1.PublishObservation(1003);
L2.PublishObservation(1004);

Это просто псевдокод и не будет работать в .NET-реализации Rx, вы должны сделать следующее:

var source1 = new Subject<int>(); //was L1
var source2 = new Subject<int>(); //was L2

var subscription = source1
    .Merge(source2)
    .Subscribe(value=>Console.WriteLine("OnNext({0})", value));


source1.OnNext(1003);
source2.OnNext(1004);

subscription.Dispose();

Теперь это не совсем соответствует первоначальному вопросу, но я не знаю, что K.Unsubscribe()должен был делать (отписаться от всех, последняя или первая подписка ?!)


Могу ли я просто заключить объект подписки в блок «с помощью»?
Роберт Ошлер

1
В этом синхронном случае вы можете, однако Rx должен быть асинхронным. В асинхронном случае вы не можете нормально использовать usingблок. Стоимость оператора подписки должна быть практически равна нулю, так что вы бы блокировали блок использования, подписывались, оставляли блок использования (таким образом, отписывались), делая код довольно бессмысленным
Ли Кэмпбелл

3

Вы правы. Пример плохо работает для нескольких IObservables.

Я предполагаю, что OnComplete () не предоставляет обратную ссылку, потому что они не хотят, чтобы IObservable сохранял его. Если бы я писал, что, вероятно, я бы поддержал несколько подписок, если бы Subscribe принимала идентификатор в качестве второго параметра, который передается обратно в вызов OnComplete (). Так что вы могли бы сказать,

K.Subscribe(L1,"L1")
K.Subscribe(L2,"L2")
K.Unsubscribe("L1")

Похоже, что .NET IObserver не подходит для нескольких наблюдателей. Но я полагаю, что ваш основной объект (в данном примере LocationReporter) может иметь

public Dictionary<String,IObserver> Observers;

и это позволит вам поддержать

K.Subscribe(L1,"L1")
K.Subscribe(L2,"L2")
K.Unsubscribe("L1")

также.

Я полагаю, что Microsoft может утверждать, что, следовательно, им нет необходимости напрямую поддерживать несколько IObservables в интерфейсах.


Я также думал, что наблюдаемая реализация может иметь список наблюдателей. Я тоже заметил, что IObserver.OnComplete()это не определяет, от кого поступил звонок. Если наблюдатель подписан на несколько наблюдаемых, он не знает, от кого отписаться. Разочаровывающим. Интересно, есть ли .NET лучший интерфейс для шаблона наблюдателя?
Ник Алексеев

Если вы хотите иметь ссылку на что-то, вы должны использовать ссылку, а не строку.
свик

Этот ответ помог мне с реальной ошибкой. Я использовал Observable.Create()для создания наблюдаемой, и используя для этого несколько исходных наблюдаемых Subscribe(). Я случайно передал завершенную наблюдаемую в одном пути кода. Это завершило мою недавно созданную наблюдаемую, хотя другие источники не были завершены. Мне понадобились целые годы, чтобы понять, что мне нужно сделать - переключиться Observable.Empty()на Observable.Never().
Олли

0

Я знаю , что это путь поздно к партии, но ...

Интерфейсы I Observable<T>и IObserver<T>являются не частью Rx ... они являются основными типами ... но Rx широко использует их.

Вы можете иметь столько наблюдателей, сколько захотите. Если вы ожидаете нескольких наблюдателей, то наблюдаемая обязанность направлять OnNext()вызовы соответствующим наблюдателям для каждого наблюдаемого события. Наблюдаемой может потребоваться список или словарь, как вы предлагаете.

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

Как указано в других ответах, нет Unsubscribe. Избавление от того, что вам дают, когда вы Subscribeобычно делаете грязную работу. Наблюдатель или его агент отвечает за удержание токена до тех пор, пока он больше не захочет получать дальнейшие уведомления. . (вопрос 1)

Итак, в вашем примере:

K.Subscribe(L1);
K.Subscribe(L2);
K.Unsubscribe();
L1.PublishObservation(1003);
L2.PublishObservation(1004);

... это было бы больше похоже на:

using ( var l1Token = K.Subscribe( L1 ) )
{
  using ( var l2Token = K.Subscribe( L2 );
  {
    L1.PublishObservation( 1003 );
    L2.PublishObservation( 1004 );
  } //--> effectively unsubscribing to L2 here

  L2.PublishObservation( 1005 );
}

... где K услышит 1003 и 1004, но не 1005.

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

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

public class SubscriptionToken<T>: IDisposable
{
  private readonly Action unsubscribe;

  private SubscriptionToken( ) { }
  public SubscriptionToken( Action unsubscribe )
  {
    this.unsubscribe = unsubscribe;
  }

  public void Dispose( )
  {
    unsubscribe( );
  }
}

... и наблюдаемое может установить поведение отказа от подписки во время подписки:

IDisposable Subscribe<T>( IObserver<T> observer )
{
  var subscriberId = Guid.NewGuid( );
  subscribers.Add( subscriberId, observer );

  return new SubscriptionToken<T>
  (
    ( ) =>
    subscribers.Remove( subscriberId );
  );
}

Если ваш наблюдатель отлавливает события из нескольких наблюдаемых, вы можете убедиться, что в самих событиях есть какая-то информация о корреляции ... как .Net события делают с sender. Это зависит от вас, имеет ли это значение или нет. Это не запечено, как вы правильно рассуждали. (вопрос 3)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.