Чи призначений IObserver <NET> .NET призначений для підписки на кілька IObservables?


9

У .NET (також тут і тут ) є інтерфейси IObservable та IObserver . Цікаво, що конкретна реалізація IObserver не має прямого посилання на IObservable. Невідомо, на кого він підписаний. Він може викликати лише передплатнику. "Будь ласка, потягніть шпильку, щоб скасувати підписку."

редагувати: Підписка реалізує IDisposable. Я думаю, ця схема була використана для запобігання проблеми з прослуханим слухачем .

Дві речі мені не зовсім зрозумілі.

  1. Чи забезпечує внутрішній клас Unsubscriber поведінку підписки та забути? Хто (і коли саме) дзвонить IDisposable.Dispose()на скасування підписки? Збір сміття (GC) не є детермінованим.
    [Відмова: загалом, я більше часу проводив із C та C ++, ніж із C #.]
  2. Що має відбутися, якщо я хочу підписати спостерігача K на спостережуваний L1, а спостерігач вже підписаний на якийсь інший спостерігається L2?

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

    Коли я запустив цей тестовий код на прикладі MSDN, спостерігач залишився підписаним на L1. Це було б своєрідно в реальному розвитку. Потенційно, існує три шляхи для покращення цього:

    • Якщо у спостерігача вже є примірник передплатників (тобто він уже підписаний), він спокійно скасовує підписку від початкового постачальника, перш ніж підписатися на нового. Такий підхід приховує той факт, що він більше не підписався на початкового постачальника, що згодом може стати сюрпризом.
    • Якщо спостерігач вже має примірник, який не підписався, то викидає виняток. Добре поведений код виклику повинен скасувати передплату спостерігача.
    • Спостерігач підписується на кілька провайдерів. Це найбільш інтригуючий варіант, але чи можна це реалізувати за допомогою IObservable та IObserver? Подивимось. Спостерігачеві можна зберегти список об'єктів, які не підписалися: по одному для кожного джерела. На жаль, IObserver.OnComplete()не надає посилання на постачальника, який його надіслав. Таким чином, реалізація IObserver з декількома постачальниками не зможе визначити, з якого потрібно скасувати підписку.
  3. Чи призначений IObserver .NET призначений для підписки на кілька IObservables?
    Чи вимагає визначення підручника шаблону спостерігача, щоб один спостерігач мав змогу підписатися на кілька постачальників послуг? Або це необов'язково і залежить від реалізації?

Відповіді:


5

Два інтерфейси насправді є частиною реактивних розширень (коротко 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 кращий інтерфейс для моделі спостерігача?
Нік Алексєєв

Якщо ви хочете мати посилання на щось, вам слід використовувати посилання, а не рядок.
svick

Ця відповідь допомогла мені з реальними помилками. Я використовував Observable.Create()для створення спостережуваного і прив'язував до нього кілька джерел спостереження за допомогою Subscribe(). Я випадково пройшов завершене спостереження в одному кодовому шляху. Це завершило моє новостворене спостереження, хоча інші джерела не були повною. Прийняв мене вік для роботи, що мені потрібно зробити - перемикач Observable.Empty()для Observable.Never().
Оллі

0

Я знаю, що це спосіб пізно до партії, але ...

Інтерфейси я 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.

Мені це все ще виглядає смішно, оскільки номінально підписки - це довготривалі речі ... часто на час дії програми. У цьому відношенні вони не відрізняються від звичайних. Чисті події.

У багатьох прикладах, які я бачив, 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.