RxJava,一个可观察的多个订阅者:publish()。autoConnect()

时间:2017-01-28 22:50:52

标签: rx-java reactive-programming

我正在玩rxJava / rxAndroid,并且有一些非常基本的东西不像我期望的那样。 我有一个可观察的和两个订阅者:

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() 来避免重复计数,但我首先想要了解这种默认行为。 每当有人订阅observable时,它就会开始发出数字序列。我明白了。因此,当Subscriber 1连接时,它开始发出项目。 Subscriber 2立即连接,为什么不能获得这些值呢?

这是我理解它的方式,从可观察的角度来看:

  1. 有人订阅了我,我应该开始发物品了 [订阅者:1] [发明项目:1,2,3]

  2. 向订阅者发送项目'1' [订阅者:1] [发明项目:2,3]

  3. 其他人订阅了我,当我完成后,我会再次发出1,2,3 [订阅者:1&amp; 2] [项目到EMIT:2,3,1,2,3]

  4. 向订阅者发送项目'2' [订阅者:1&amp; 2] [项目到EMIT:3,1,2,3]

  5. 向订阅者发送项目'3' [订阅者:1&amp; 2] [项目到EMIT:1,2,3]

  6. 向订阅者发送项目'1' [订阅者:1&amp; 2] [项目到EMIT:2,3]

  7. ...

  8. 但这不是它的工作原理。 就像它们是两个独立的可观察者一样。这让我很困惑,他们为什么不把这些物品交给所有订户?

    加成:

    publish().autoConnect()如何解决问题? 让我们分解吧。 publish()给了我一个可连接的可观察对象。可连接的observable就像一个常规的observable,但你可以告诉它何时连接。然后我继续通过拨打autoConnect()

    告诉它立即连接

    通过这样做......难道我没有得到与我开始时相同的东西吗?一个普通的常规观察。 运营商似乎互相取消。

    我可以闭嘴并使用publish().autoconnect()。但我想更多地了解可观察物是如何工作的。

    谢谢!

2 个答案:

答案 0 :(得分:15)

这是因为实际上这是两个独立的可观察者。他们是'#34;产生&#34;当你调用subscribe()时。因此,您提供的步骤在第3步和第3步的意义上是不正确的。 4只是1&amp; 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);

}

你会注意到,第一秒(第一次Thread.sleep()调用)没有任何反应,就在调用dataStream.connect()之后,排放就会发生。

refCount()接收一个ConnectableObservable,并通过计算当前订阅的订阅者数来向订阅者隐藏调用connect()的需要。它的作用是在第一次订阅时调用connect()并且在最后取消订阅之后取消订阅原始可观察对象。

关于publish().autoConnect()的相互取消,之后你会得到一个可观察的但是它有一个特殊的属性,比如原来的observable通过互联网进行API调用(持续10秒),当你使用时如果没有share(),那么您将最终获得与服务器一样多的并行查询,因为这些订阅超过了10秒。另一方面,share()只有一个电话。

如果共享的可观察对象以非常快的速度完成其工作(例如just(1,2,3)),您将看不到任何好处。

autoConnect() / refCount()为您提供了一个您订阅的中间可观察对象,而不是原始的可观察对象。

如果您有兴趣深入了解本书:Reactive Programming with RxJava

答案 1 :(得分:9)

常规(冷)可观察

Observable的核心是subscribe功能。每次新观察者订阅时,它都会作为参数传递给此函数。该功能的作用是将数据提供给单个观察者。它通过调用observer.onNext方法完成此操作。它可以立即执行此操作(如just),或通过某些调度程序(例如interval),或从后台线程或回调(例如,通过启动某些异步任务)。

我在上面突出显示单词 single ,因为这是该函数调用时唯一的观察者。如果您多次订阅此类可观察对象,则会为每个订阅者调用其subscribe函数。

这样的数据源称为冷可观察

调度程序

应用subscribeOn运算符会在您的subscribe调用和原始可观察的subscribe函数之间添加中间步骤。您不再直接呼叫它,而是通过指定的调度程序安排呼叫。

observeOn为您的观察者的所有onNext次调用添加了类似的中间步骤。

在您的示例中,subscribe函数被调用两次,即数据系列生成两次。调用是通过多线程io调度程序调度的,因此这些调用不是在主线程上发生,而是在其他两个线程上发生,几乎同时发生。两个线程都开始调用两个订户的onNext方法。请记住,每个线程只知道自己的订户。 onNext调度由mainThread调度程序调度,这是单线程的,即它们不能同时发生但需要以某种方式排队。 严格来说,无法保证这些电话的订购。它取决于各种因素,具体是针对具体实施的。尝试将just替换为interval(这会在消息之间引入延迟),并且您会看到消息将以不同的顺序到达。

热观察

publish运算符使您的observable hot ,又名可连接。它为subscribe函数添加了中间步骤 - 仅调用一次,并向onNext方法调用 - 这些将传播到所有已订阅的可观察对象。换句话说,它允许多个订阅者共享单个订阅

准确地说,当您调用subscribe方法时,会调用connect函数。有两个运算符会自动为您调用connect

  • 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的一个订阅。即您不必为每个新订阅者发出新的消息系列。