Project Reactor 3中的publishOn vs subscribeOn

时间:2018-01-03 07:54:59

标签: reactive-programming publish-subscribe project-reactor publisher reactive-streams

我使用的是publishOn vs subscribeOn,它们的流量如下:

    System.out.println("*********Calling Concurrency************");
    List<Integer> elements = new ArrayList<>();
    Flux.just(1, 2, 3, 4)
      .map(i -> i * 2)
      .log()
      .publishOn(Schedulers.elastic())
      .subscribeOn(Schedulers.parallel())
      .subscribe(elements::add);
    System.out.println("-------------------------------------");

虽然,当我同时使用它们时,日志中不会打印任何内容。 但是当我只使用publishOn时,我得到了以下信息日志:

*********Calling Concurrency************
[info] | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription)
[info] | request(256)
[info] | onNext(1)
[info] | onNext(2)
[info] | onNext(3)
[info] | onNext(4)
[info] | onComplete()
-------------------------------------

比subscribeOn更推荐publishOn吗?或者它比subscribeOn更有偏好?两者之间有什么区别,何时使用哪个?

3 个答案:

答案 0 :(得分:1)

这是我得到的一个小文档:

  

publishOn以与订户链中间的任何其他运营商相同的方式应用。它接收来自下游的信号,并在从关联的调度程序对工作程序执行回调时重放它们。因此,它会影响后续运算符的执行位置(直到另一个publishOn被链接)。

     

subscribeOn适用于订阅过程,构建后向链时。因此,无论您将subscribeOn放在链中的哪个位置,它总是会影响源发射的上下文。但是,这不会影响后续调用publishOn的行为。他们仍然在它们之后切换链的一部分的执行上下文。

  

publishOn强制下一个运算符(以及可能在下一个运算符之后的后续运算符)在不同的线程上运行。类似地,subscribeOn强制前一个运算符(以及可能的运算符在前一个运算符之前)在另一个线程上运行。

答案 1 :(得分:1)

我花了一些时间来理解它,也许是因为publishOn通常是在subscribeOn之前解释的,所以这是一个可能更简单的外行解释。

subscribeOn意味着在指定的调度程序工作程序(其他线程)上运行初始源发射,例如subscribe(), onSubscribe() and request(),并且对于任何后续操作(例如onNext/onError/onComplete, map etc)也是如此,无论如果位置为scribeOn(),则会发生这种情况

如果您在流畅的调用中没有执行任何publishOn,就这样,一切都将在该线程上运行。

但是,假设您在中间调用publishOn(),那么随后的任何操作员调用都将在提供的调度程序工作程序上运行到publishOn()

这是一个例子

Consumer<Integer> consumer = s -> System.out.println(s + " : " + Thread.currentThread().getName());

Flux.range(1, 5)
        .doOnNext(consumer)
        .map(i -> {
          System.out.println("Inside map the thread is " + Thread.currentThread().getName());
          return i * 10;
        })
        .publishOn(Schedulers.newElastic("First_PublishOn()_thread"))
        .doOnNext(consumer)
        .publishOn(Schedulers.newElastic("Second_PublishOn()_thread"))
        .doOnNext(consumer)
        .subscribeOn(Schedulers.newElastic("subscribeOn_thread"))
        .subscribe();

结果将是


1 : subscribeOn_thread-4
Inside map the thread is subscribeOn_thread-4
2 : subscribeOn_thread-4
Inside map the thread is subscribeOn_thread-4
10 : First_PublishOn()_thread-6
3 : subscribeOn_thread-4
Inside map the thread is subscribeOn_thread-4
20 : First_PublishOn()_thread-6
4 : subscribeOn_thread-4
10 : Second_PublishOn()_thread-5
30 : First_PublishOn()_thread-6
20 : Second_PublishOn()_thread-5
Inside map the thread is subscribeOn_thread-4
30 : Second_PublishOn()_thread-5
5 : subscribeOn_thread-4
40 : First_PublishOn()_thread-6
Inside map the thread is subscribeOn_thread-4
40 : Second_PublishOn()_thread-5
50 : First_PublishOn()_thread-6
50 : Second_PublishOn()_thread-5

您可以看到第一个doOnNext()和随后的map()在名为subscribeOn_thread的线程上运行,直到调用任何publishOn()为止,然后进行任何后续调用在提供的调度程序上运行该publishOn(),对于任何后续调用都将再次发生,直到有人调用另一个publishOn()

答案 2 :(得分:0)

以下是摘自优秀博客文章https://spring.io/blog/2019/12/13/flight-of-the-flux-3-hopping-threads-and-schedulers

的摘录

publishOn

这是您想跳线程时所需的基本运算符。来自其来源的传入信号会在给定的调度程序上发布,从而有效地将线程切换到该调度程序的工作线程之一。

这对onNextonCompleteonError信号有效。也就是说,信号从上游源流向下游用户。

因此,从本质上讲,出现在此运算符下面的每个处理步骤都将在新的Scheduler上执行,直到另一个运算符再次切换(例如另一个publishOn)为止。

Flux.fromIterable(firstListOfUrls) //contains A, B and C
    .publishOn(Schedulers.boundedElastic())
    .map(url -> blockingWebClient.get(url))
    .subscribe(body -> System.out.println(Thread.currentThread().getName + " from first list, got " + body));

Flux.fromIterable(secondListOfUrls) //contains D and E
    .publishOn(Schedulers.boundedElastic())
    .map(url -> blockingWebClient.get(url))
    .subscribe(body -> System.out.prinln(Thread.currentThread().getName + " from second list, got " + body));

输出

boundedElastic-1 from first list, got A
boundedElastic-2 from second list, got D
boundedElastic-1 from first list, got B
boundedElastic-2 from second list, got E
boundedElastic-1 from first list, got C

subscribeOn

此运算符更改执行subscription方法的位置。由于订阅信号向上流动,因此直接影响源Flux订阅的位置并开始生成数据。

因此,它似乎可以向上和向下作用于操作员反应性链的各个部分(只要没有publishOn被抛出):

final Flux<String> fetchUrls(List<String> urls) {
  return Flux.fromIterable(urls)
           .map(url -> blockingWebClient.get(url));
}

// sample code:
fetchUrls(A, B, C)
  .subscribeOn(Schedulers.boundedElastic())
  .subscribe(body -> System.out.println(Thread.currentThread().getName + " from first list, got " + body));

fetchUrls(D, E)
  .subscribeOn(Schedulers.boundedElastic())
  .subscribe(body -> System.out.prinln(Thread.currentThread().getName + " from second list, got " + body));

输出

boundedElastic-1 from first list, got A
boundedElastic-2 from second list, got D
boundedElastic-1 from first list, got B
boundedElastic-2 from second list, got E
boundedElastic-1 from first list, got C