IObserver <T> di .NET era destinato alla sottoscrizione di più IObservable?


9

Ci sono interfacce IObservable e IObserver in .NET (anche qui e qui ). È interessante notare che l'implementazione concreta di IObserver non contiene un riferimento diretto a IObservable. Non sa a chi è abbonato. Può solo invocare l'iscritto. "Per favore, tira il perno per annullare l'iscrizione."

modifica: il sottoscrittore implementa il IDisposable. Penso che questo schema sia stato utilizzato per prevenire il problema dell'ascoltatore decaduto .

Però due cose non sono del tutto chiare per me.

  1. La classe Unsubscriber interna fornisce il comportamento di iscrizione e dimentica? Chi (e quando esattamente) chiama IDisposable.Dispose()l'Iscritto? Il Garbage Collector (GC) non è deterministico.
    [Dichiarazione di non responsabilità: nel complesso, ho trascorso più tempo con C e C ++ che con C #.]
  2. Cosa dovrebbe accadere se voglio iscrivere un osservatore K a un L1 osservabile e l'osservatore è già abbonato ad un altro L2 osservabile?

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

    Quando ho eseguito questo codice di test sull'esempio di MSDN, l'osservatore è rimasto abbonato a L1. Ciò sarebbe peculiare nello sviluppo reale. Potenzialmente, ci sono 3 strade per migliorare questo:

    • Se l'osservatore ha già un'istanza di annullamento iscrizione (ovvero è già sottoscritta), allora annulla l'iscrizione in silenzio al provider originale prima di iscriversi a una nuova. Questo approccio nasconde il fatto che non è più abbonato al fornitore originale, che potrebbe diventare una sorpresa in seguito.
    • Se l'osservatore ha già un'istanza di annullamento sottoscrizione, viene generata un'eccezione. Un codice di chiamata ben educato deve annullare l'iscrizione in modo esplicito all'osservatore.
    • Observer si abbona a più provider. Questa è l'opzione più intrigante, ma può essere implementata con IObservable e IObserver? Vediamo. È possibile che l'osservatore mantenga un elenco di oggetti non iscritti: uno per ogni sorgente. Sfortunatamente, IObserver.OnComplete()non fornisce un riferimento al provider che lo ha inviato. Pertanto, l'implementazione di IObserver con più provider non sarebbe in grado di determinare da quale annullare l'iscrizione.
  3. IObserver di .NET era destinato alla sottoscrizione a più IObservable?
    La definizione da manuale del modello di osservatore richiede che un osservatore sia in grado di abbonarsi a più fornitori? O è facoltativo e dipende dall'implementazione?

Risposte:


5

Le due interfacce fanno effettivamente parte delle estensioni reattive (abbreviato in Rx), dovresti usare quella libreria praticamente ogni volta che vuoi usarle.

Le interfacce sono tecnicamente in mscrolib, non in nessuno degli assiemi Rx. Penso che ciò faciliti l'interoperabilità: in questo modo librerie come TPL Dataflow possono fornire ai membri che lavorano con quelle interfacce , senza fare realmente riferimento a Rx.

Se usi Rx Subjectcome implementazione di IObservable, Subscriberestituirai uno IDisposableche può essere utilizzato per annullare l'iscrizione:

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

Giusto per chiarire alcune cose che sono ben documentate nell'ufficiale Linee guida progettazione di Rx e ampiamente sul mio sito Web IntroToRx.com :

  • Non fare affidamento sul GC per ripulire gli abbonamenti. Coperto in dettaglio qui
  • Non c'è Unsubscribe metodo Ti iscrivi a una sequenza osservabile e ti viene dato un abbonamento . È quindi possibile disporre di tale abbonamento indicando che non si desidera più invocare i callback.
  • Una sequenza osservabile non può essere completata più di una volta (vedere la sezione 4 delle Linee guida per la progettazione di Rx).
  • Esistono numerosi modi per consumare più sequenze osservabili. Esistono anche molte informazioni su Reactivex.io e di nuovo su IntroToRx.

Per essere specifici e rispondere direttamente alla domanda originale, il tuo utilizzo è di ritorno. Non spingi molte sequenze osservabili in un singolo osservatore. Componi sequenze osservabili in un'unica sequenza osservabile. Ti iscrivi quindi a quella singola sequenza.

Invece di

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

Che è solo pseudo codice e non funzionerebbe nell'implementazione .NET di Rx, dovresti fare quanto segue:

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();

Ora, questo non corrisponde esattamente alla domanda iniziale, ma non so che cosa K.Unsubscribe()avrebbe dovuto fare (annullare l'iscrizione a tutti, l'ultimo o il primo abbonamento ?!)


Posso semplicemente racchiudere l'oggetto di sottoscrizione in un blocco "using"?
Robert Oschler,

1
In questo caso sincrono puoi, tuttavia Rx dovrebbe essere asincrono. Nel caso asincrono, normalmente non è possibile utilizzare il usingblocco. Il costo per una dichiarazione di abbonamento dovrebbe essere praticamente pari a zero, quindi si annulla il blocco utilizzando, si iscrive, si lascia il blocco utilizzando (quindi si annulla l'iscrizione) rendendo il codice piuttosto inutile
Lee Campbell,

3

Hai ragione. L'esempio funziona male per più IObservable.

Immagino che OnComplete () non fornisca un riferimento perché non vogliono che IObservable debba tenerlo in giro. Se scrivessi che probabilmente supporterei più abbonamenti facendo in modo che Abbonati prenda un identificatore come secondo parametro, che viene restituito alla chiamata OnComplete (). Quindi potresti dire

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

Allo stato attuale, sembra che IObserver .NET non sia adatto a più osservatori. Ma suppongo che il tuo oggetto principale (LocationReporter nell'esempio) potrebbe avere

public Dictionary<String,IObserver> Observers;

e ciò ti consentirebbe di supportare

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

anche.

Suppongo che Microsoft potrebbe sostenere che pertanto non è necessario che supportino direttamente più IObservable nelle interfacce.


Pensavo anche che l'implementazione osservabile possa avere un elenco di osservatori. Anch'io ho notato che IObserver.OnComplete()non identifica da chi proviene la chiamata. Se l'osservatore è abbonato a più di un osservabile, allora non sa da chi annullare l'iscrizione. Deludente. Mi chiedo, .NET ha un'interfaccia migliore per il modello di osservatore?
Nick Alexeev,

Se vuoi avere un riferimento a qualcosa, dovresti effettivamente usare un riferimento, non una stringa.
svick,

Questa risposta mi ha aiutato con un bug nella vita reale. Stavo usando Observable.Create()per costruire un osservabile, e incatenando diversi osservabili sorgente usando Subscribe(). Ho inavvertitamente superato un osservabile completato in un percorso di codice. Ciò ha completato il mio osservabile appena creato, anche se le altre fonti non erano complete. Mi ci sono voluti anni per capire cosa dovevo fare: passare Observable.Empty()a Observable.Never().
Olly,

0

So che è molto tardi per la festa, ma ...

Le interfacce I Observable<T>e nonIObserver<T> fanno parte di Rx ... sono tipi di core ... ma Rx ne fa un ampio uso.

Sei libero di avere tutti gli osservatori (o meno) che desideri. Se si prevedono più osservatori, è responsabilità dell'osservatore indirizzare le OnNext()chiamate agli osservatori appropriati per ciascun evento osservato. L'osservabile potrebbe aver bisogno di un elenco o di un dizionario come suggerisci.

Ci sono buoni casi per permetterne solo uno - e buoni casi per permetterne molti. Ad esempio, in un'implementazione CQRS / ES, si potrebbe applicare un singolo gestore di comandi per il comando tipo di comando su un autobus, mentre si potrebbe notificare diversi trasformate in lettura-side per un dato evento di tipo nel negozio evento.

Come indicato in altre risposte, non c'è Unsubscribe. Smaltimento di ciò che ti viene dato quando fai Subscribegeneralmente il lavoro sporco. L'osservatore, o un suo agente, è responsabile di trattenere il token fino a quando non desidera più ricevere ulteriori notifiche . (domanda 1)

Quindi, nel tuo esempio:

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

... sarebbe più simile a:

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 );
}

... dove K avrebbe ascoltato 1003 e 1004 ma non 1005.

Per me, questo sembra ancora divertente perché nominalmente, gli abbonamenti sono cose di lunga durata ... spesso per la durata del programma. Non sono diversi in questo senso rispetto ai normali eventi .Net.

In molti esempi che ho visto, il Disposetoken funziona per rimuovere l'osservatore dall'elenco degli osservatori osservabili. Preferisco che il token non porti così tanta conoscenza in giro ... e quindi ho generalizzato i miei token di abbonamento per chiamare semplicemente un lambda passato (con informazioni identificative acquisite al momento dell'iscrizione:

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

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

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

... e l'osservabile può installare il comportamento di annullamento dell'iscrizione durante la sottoscrizione:

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

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

Se il tuo osservatore sta rilevando eventi da più osservabili, potresti voler assicurarti che ci sia qualche tipo di informazione di correlazione negli eventi stessi ... come fanno gli eventi .Net con il sender. Sta a te decidere se è importante o meno. Non è cotto, come hai giustamente ragionato. (Domanda 3)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.