web-dev-qa-db-ja.com

RxJava、1つの監視可能な複数のサブスクライバー:publish()。autoConnect()

私はrxJava/rxAndroidをいじっていますが、期待通りに動作しない非常に基本的なものがあります。この1つの観測可能と2つのサブスクライバーがあります。

_Observable<Integer> dataStream = Observable.just(1, 2, 3).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());

Log.d(TAG, "subscribing sub1...");
dataStream.subscribe(v -> System.out.println("Subscriber #1: "+ integer));

Log.d(TAG, "subscribing sub2...");
dataStream.subscribe(v -> System.out.println("Subscriber #2: "+ integer));
_

そして、これは出力です:

_D/RxJava: subscribing sub1...
D/RxJava: subscribing sub2...
D/RxJava: Subscriber #1: 1
D/RxJava: Subscriber #1: 2
D/RxJava: Subscriber #1: 3
D/RxJava: Subscriber #2: 1
D/RxJava: Subscriber #2: 2
D/RxJava: Subscriber #2: 3
_

これで、publish().autoConnect()を使用してカウントを繰り返すことを回避できることがわかりましたが、最初にこのデフォルトの動作を理解しようとしています。誰かがオブザーバブルにサブスクライブするたびに、番号シーケンスの発行が開始されます。わかった。したがって、_Subscriber 1_が接続されると、アイテムの出力が開始されます。 _Subscriber 2_はすぐに接続しますが、なぜ値も取得しないのですか?

これは私がそれを理解する方法です、オブザーバブルの観点から:

  1. 誰かが私を購読しました
    [加入者:1] [放出するアイテム:1,2,3]

  2. サブスクライバーにアイテム「1」を発行します
    [加入者:1] [放出するアイテム:2,3]

  3. 他の誰かが私にサブスクライブして、終わったらもう一度1,2,3を放出します
    [加入者:1&2] [放出するアイテム:2,3,1,2,3]

  4. サブスクライバーにアイテム「2」を発行します
    [加入者:1&2] [放出するアイテム:3,1,2,3]

  5. サブスクライバーにアイテム「3」を発行します
    [加入者:1&2] [放出するアイテム:1,2,3]

  6. サブスクライバーにアイテム「1」を発行します
    [加入者:1&2] [放出するアイテム:2,3]

  7. ...

しかし、これはその仕組みではありません。まるで2つの別々のオブザーバブルであるかのようです。これは私を混乱させます、なぜ彼らはすべての購読者にアイテムを渡さないのですか?

ボーナス:

publish().autoConnect()はどのように問題を修正しますか?分解しましょう。 publish()は、接続可能なオブザーバブルを提供します。接続可能なオブザーバブルは、通常のオブザーバブルに似ていますが、いつ接続するかを指定できます。次に、autoConnect()を呼び出してすぐに接続するように伝えます

そうすることによって...私は私が始めたのと同じものを手に入れませんか?普通の定期的な観測可能。 演算子は互いにキャンセルしているように見えます。

シャットダウンしてpublish().autoconnect()を使用できます。しかし、オブザーバブルがどのように機能するかについてもっと理解したいと思います。

ありがとう!

13
FRR

これは、実際には、これらが2つの別個のオブザーバブルであるためです。 subscribe()を呼び出すと、「スポーン」されます。したがって、ステップ3と4は1と2だけですが、異なるオブザーバブルにあるという意味で、指定したステップは正しくありません。

しかし、ロギングが行われるスレッドのために、それらは1 1 1 2 2 2として表示されます。 observeOn()部分を削除すると、織り交ぜられた放出が見られます。この実行コードを表示するには、次をご覧ください。

_@Test
public void test() throws InterruptedException {
    final Scheduler single = Schedulers.single();
    final long l = System.nanoTime();
    Observable<Long> dataStream =
            Observable.just(1, 2, 3)
                    .map(i -> System.nanoTime())
                    .subscribeOn(Schedulers.computation());
                    //.observeOn(single);

    dataStream.subscribe(i -> System.out.println("1  " + Thread.currentThread().getName() + " " + (i - l)));
    dataStream.subscribe(i -> System.out.println("2  " + Thread.currentThread().getName() + " " + (i - l)));

    Thread.sleep(1000);
}
_

出力、少なくとも私の実行では(通知スレッド名):

_1  RxComputationThreadPool-1 135376988
2  RxComputationThreadPool-2 135376988
1  RxComputationThreadPool-1 135486815
2  RxComputationThreadPool-2 135537383
1  RxComputationThreadPool-1 135560691
2  RxComputationThreadPool-2 135617580
_

observeOn()を適用すると、次のようになります。

_1  RxSingleScheduler-1 186656395
1  RxSingleScheduler-1 187919407
1  RxSingleScheduler-1 187923753
2  RxSingleScheduler-1 186656790
2  RxSingleScheduler-1 187860148
2  RxSingleScheduler-1 187864889
_

正しく指摘したように、必要なものを取得するには、publish().refcount()または単にshare()(エイリアスです)演算子が必要です。

これは、publish()ConnectableObservableを作成するためです。これは、connect()メソッドを介して指示されるまでアイテムの発行を開始しません。その場合、これを行うと:

_@Test
public void test() throws InterruptedException {
    final Scheduler single = Schedulers.single();
    final long l = System.nanoTime();
    ConnectableObservable<Long> dataStream =
            Observable.just(1, 2, 3)
                    .map(i -> System.nanoTime())
                    .subscribeOn(Schedulers.computation())
                    .observeOn(single)
                    .publish();

    dataStream.subscribe(i -> System.out.println("1  " + (i - l)));
    dataStream.subscribe(i -> System.out.println("2  " + (i - l)));

    Thread.sleep(1000);
    dataStream.connect();
    Thread.sleep(1000);

}
_

最初の2番目(最初のThread.sleep()呼び出し)には何も起こらず、dataStream.connect()が呼び出された直後に排出が発生することに気付くでしょう。

refCount()はConnectableObservableを受け取り、現在サブスクライブしているサブスクライバーの数をカウントすることで、サブスクライバーからconnect()を呼び出す必要性を隠します。それは、最初のサブスクリプション時にconnect()を呼び出し、最後のサブスクリプション解除後に元のオブザーバブルからサブスクリプション解除されます。

publish().autoConnect()の相互キャンセルに関しては、その後、オブザーバブルを取得しますが、1つの特別なプロパティがあります。元のオブザーバブルは、使用時にインターネット経由でAPI呼び出しを実行します(10秒持続) share()なしでは、それらの10秒間のサブスクリプションと同数のサーバーへの並列クエリが発生します。一方、share()を使用すると、呼び出しは1つだけになります。

共有されているobservableが(just(1,2,3)のように)非常に高速にその作業を完了する場合、その利点は表示されません。

autoConnect()/refCount()は、元のオブザーバブルの代わりに、サブスクライブする中間オブザーバブルを提供します。

この本に興味がある場合: RxJavaを使用したリアクティブプログラミング

15
MatBos

通常の(冷たい)観測可能

Observableの中核はsubscribe関数です。新しいオブザーバーがサブスクライブするたびに、パラメーターとしてこの関数に渡されます。この関数は、データをそのsingleオブザーバーに送ります。 _observer.onNext_メソッドを呼び出すことでこれを行います。これは、すぐに(justのように)、スケジューラ(たとえば、interval)を介して、またはバックグラウンドスレッドまたはコールバックから(たとえば、非同期タスクを起動することによって)行われます。

上記のWordsingleを強調表示したのは、この関数が呼び出されたときにこの関数が知っている唯一のオブザーバだからです。このようなobservableを複数回サブスクライブする場合、そのsubscribe関数はすべてのサブスクライバーに対して呼び出されます。

このようなデータソースはcold observableと呼ばれます。

スケジューラー

subscribeOn演算子を適用すると、subscribe呼び出しと元のobservableのsubscribe関数の間に中間ステップが追加されます。直接呼び出すことはなくなりましたが、指定されたスケジューラを介して呼び出しをスケジュールします。

observeOnは、オブザーバーのすべてのonNext呼び出しに同様の中間ステップを追加します。

あなたの例では、subscribe関数が2回呼び出されます。つまり、データ系列が2回生成されます。呼び出しはマルチスレッドioスケジューラを介してスケジュールされるため、これらの呼び出しはメインスレッドではなく、他の2つのスレッドでほぼ同時に発生します。両方のスレッドは、2つのサブスクライバーのonNextメソッドの呼び出しを開始します。各スレッドは自身のサブスクライバーのみを知っていることに注意してください。 onNext呼び出しはmainThreadスケジューラによってスケジュールされます。スケジューラはシングルスレッドです。つまり、同時に発生することはできませんが、何らかの方法でキューに入れる必要があります。 厳密に言えば、これらの呼び出しの順序についての保証はありません。さまざまな要因に依存し、実装に固有です。 justintervalに置き換えてみてください(これにより、メッセージ間の遅延が発生します)。メッセージが異なる順序で到着することがわかります。

熱い観測可能

publish演算子を使用すると、観測可能hot、別名connectablesubscribe関数(これは一度だけ呼び出されます)とonNextメソッドの両方に中間ステップを追加します-これらはサブスクライブされたすべてのオブザーバブルに伝搬されます。つまり、複数のサブスクライバーが単一のサブスクリプションを共有できるようにします

正確には、subscribeメソッドを呼び出すと、connect関数が呼び出されます。 connectを自動的に呼び出す2つの演算子があります。

  • autoConnectは、最初のサブスクライバーが入ったときにconnectメソッドを呼び出します。しかし、決して切断しません。
  • refCountは、最初のサブスクライバーが着信するとconnectを呼び出し、最後のサブスクライバーがサブスクリプションを解除すると自動的に切断します。新しいサブスクライバーが入ると、再接続します(subscribe関数を再度呼び出します)。

publish().refCount()は一般的な組み合わせなので、ショートカットはshare()です。

教育のために、shareがある場合とない場合の次のコードを試してください。

_Observable<Long> dataStream = Observable.interval(100, TimeUnit.MILLISECONDS)
        .take(3)
        .share();
System.out.println("subscribing A");
dataStream.subscribe(v -> System.out.println("A got " + v));
TimeUnit.MILLISECONDS.sleep(150);
System.out.println("subscribing B");
dataStream.subscribe(v -> System.out.println("B got " + v));
TimeUnit.SECONDS.sleep(1);
_

元の質問への回答

1)コールドオブザーバブルは常に単一のサブスクライバーを扱います。したがって、時間図は次のようになります。

_subscribed first subscriber
[SUBSCRIBER: 1][ITEMS TO EMIT: 1,2,3]
subscribed second subscriber
[SUBSCRIBER: 1][ITEMS TO EMIT: 1,2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 1,2,3]
emit "1" to subscriber 1
[SUBSCRIBER: 1][ITEMS TO EMIT: 2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 1,2,3]
emit "1" to subscriber 2
[SUBSCRIBER: 1][ITEMS TO EMIT: 2,3]
[SUBSCRIBER: 2][ITEMS TO EMIT: 2,3]
...
_

ただし、マルチスレッドの競合のため、順序は保証されません。

2)publishautoConnectは互いにキャンセルしません。追加するだけです。

_dataSource = ...;
dataSourceShared = dataSource.publish().autoConnect();
_

これで、複数のサブスクライバーをdataSourceSharedにサブスクライブすると、元のdataSourceへのサブスクリプションが1つだけになります。つまり新しい加入者ごとに新しい一連のメッセージを送信する必要はありません。

9