.NETのIObserver <T>は、複数のIObservableをサブスクライブすることを目的としていましたか?


9

.NETにはIObservableおよびIObserverインターフェイスがあります(ここここも)。興味深いことに、IObserverの具体的な実装は、IObservableへの直接参照を保持していません。誰が購読しているかはわかりません。購読解除者のみを呼び出すことができます。「登録を解除するにはピンを抜いてください。」

編集:購読解除者はを実装しIDisposableます。この方式は、リスナー失効問題を防ぐために使用されたと思います。

しかし、2つのことは完全に明確ではありません。

  1. 内部のUnsubscriberクラスはsubscribe-and-forget動作を提供しますか?誰が(正確にはいつ)登録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にサブスクライブしたままでした。これは実際の開発に特有のものです。潜在的に、これを改善する3つの方法があります。

    • オブザーバーがすでにサブスクライブ解除インスタンスを持っている(つまり、すでにサブスクライブしている)場合、オブザーバーは新しいプロバイダーにサブスクライブする前に、元のプロバイダーから静かにサブスクライブ解除します。このアプローチにより、元のプロバイダーにサブスクライブされなくなったという事実が隠されます。これは、後で驚くかもしれません。
    • オブザーバーがすでにサブスクライブ解除インスタンスを持っている場合は、例外がスローされます。正常に動作する呼び出しコードは、オブザーバーを明示的にサブスクライブ解除する必要があります。
    • オブザーバーは複数のプロバイダーにサブスクライブします。これは最も興味深いオプションですが、IObservableとIObserverで実装できますか?どれどれ。オブザーバがサブスクライバオブジェクトのリストを保持することは可能です:ソースごとに1つ。残念ながら、IObserver.OnComplete()それを送信したプロバイダーに戻って参照を提供していません。したがって、複数のプロバイダーを使用するIObserver実装は、どのプロバイダーからサブスクライブを解除するかを決定できません。
  3. .NETのIObserverは、複数のIObservableをサブスクライブすることを目的としていましたか?
    テキスト形式のオブザーバーパターンの定義では、1人のオブザーバーが複数のプロバイダーにサブスクライブできる必要があるか?それともオプションで実装に依存していますか?

回答:


5

2つのインターフェースは実際にはReactive Extensions(略してRx)の一部です。使用するときはいつでも、そのライブラリをかなり使用する必要があります。

インターフェイスは技術的にmscrolibにあり、Rxアセンブリにはありません。これは相互運用性を容易にするためだと思います。このようにして、TPL Dataflowのようなライブラリは、実際にRxを参照することなく、これらのインターフェイス動作するメンバーを提供できます。

Subject実装としてRxを使用する場合は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設計ガイドラインに詳しく文書化されているいくつかのことを明確にするために、私のWebサイトIntroToRx.comに詳細を記載します。

  • サブスクリプションのクリーンアップはGCに依存しません。詳細はこちら
  • Unsubscribe方法はありません。オブザーバブルシーケンスをサブスクライブすると、サブスクリプションが与えられます。その後、そのサブスクリプションを破棄して、コールバックを呼び出さないようにすることができます。
  • 観測可能なシーケンスは2回以上完了できません(Rx設計ガイドラインのセクション4を参照)。
  • 複数の監視可能なシーケンスを使用する方法は多数あります。Reactivex.ioやIntroToRxにも、それに関する豊富な情報があります。

具体的に説明し、元の質問に直接回答するには、使用方法を逆にしてください。多くの観察可能なシーケンスを単一のオブザーバーにプッシュしません。観察可能なシーケンスを単一の観察可能なシーケンスに構成します。次に、その単一のシーケンスをサブスクライブします。

の代わりに

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

これは単なる擬似コードであり、Rxの.NET実装では機能しません。以下を実行する必要があります。

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()ません(すべて、最後、または最初のサブスクリプションから登録解除しますか?)。


サブスクリプションオブジェクトを「using」ブロックに単純に含めることはできますか?
Robert Oschler、2015年

1
この同期の場合は可能ですが、Rxは非同期であることが想定されています。非同期の場合、通常はusingブロックを使用できません。サブスクリプションステートメントのコストは実質的にゼロでなければならないため、usingブロックをネット化し、サブスクライブし、usingブロックをそのままにして(したがってサブスクライブを解除)、コードをかなり無意味にします
Lee Campbell

3

あなたが正しい。この例は、複数のIObservableに対してはうまく機能しません。

OnComplete()は、IObservableがそれを保持する必要があることを望まないため、参照を提供しないと思います。もし、Subscribeが識別子を2番目のパラメータとして受け取り、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は、インターフェイスで複数のIObservableを直接サポートする必要がないと主張することができると思います。


また、監視可能な実装にはオブザーバーのリストを含めることができると考えていました。私も、それIObserver.OnComplete()が誰からの電話なのかを特定できないことに気づきました。オブザーバーが複数のオブザーバブルにサブスクライブしている場合、オブザーバーは誰からサブスクライブを解除すべきかわかりません。クライマクティック。.NETはオブザーバーパターンのためのより良いインターフェースを持っていますか?
Nick Alexeev 14

何かへの参照が必要な場合は、実際には文字列ではなく参照を使用する必要があります。
2014

この答えは、実際のバグで私を助けました。私はObservable.Create()オブザーバブルを構築するために使用していて、いくつかのソースオブザーバブルをそれに使用してチェーン化していましたSubscribe()。誤って1つのコードパスで完成したオブザーバブルを渡しました。これで、他のソースが完全ではなかったとしても、新しく作成したオブザーバブルが完成しました。スイッチを-私が行うために必要なものを仕事に私の年齢を取ったObservable.Empty()ためObservable.Never()
Olly 2017

0

私は、これは知っているのパーティーに遅れて、しかし...

I Observable<T>とRxの一部でIObserver<T>ないインターフェイス...それらはコアタイプです...しかしRxはそれらを広範囲に使用します。

オブザーバーは好きなだけ(または少ない)自由に選択できます。複数のオブザーバーが予想される場合は、観測OnNext()されたイベントごとに適切なオブザーバーに呼び出しをルーティングするのがオブザーバブルの責任です。オブザーバブルには、あなたが提案するリストや辞書が必要になる場合があります。

1つだけを許可する場合と、多くを許可する場合の両方があります。たとえば、CQRS / ES実装では、コマンドストアのコマンドタイプごとに単一のコマンドハンドラーを適用する一方で、イベントストア内の特定のイベントタイプのいくつかの読み取り側変換を通知できます。

他の回答で述べたように、はありませんUnsubscribeSubscribe一般的に汚い仕事をするときに与えられたものを処分します。オブザーバーまたはそのエージェントは、それ以上の通知の受信が不要になるまでトークン保持する責任があります。(質問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)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.