RxJava调度程序的用例


253

在RxJava中,有5种不同的调度程序可供选择:

  1. Immediate():创建并返回一个Scheduler,该Scheduler立即在当前线程上执行工作。

  2. trampoline():创建并返回一个调度程序,该调度程序在当前工作完成后将要在当前线程上执行的工作排队。

  3. newThread():创建并返回一个Scheduler,该Scheduler为每个工作单元创建一个新的Thread。

  4. Calculation():创建并返回用于计算工作的Scheduler。这可以用于事件循环,处理回调和其他计算工作。不要在此调度程序上执行IO绑定工作。使用计划程序。io()代替。

  5. io():创建并返回用于IO绑定工作的Scheduler。该实现由Executor线程池支持,该线程池将根据需要增长。这可用于异步执行阻塞IO。不要在此调度程序上执行计算工作。使用计划程序。Calculation()代替。

问题:

前三个调度程序很容易说明。但是,我对计算io有点困惑。

  1. 什么是“ IO绑定工作”?它用于处理流(java.io)和文件(java.nio.files)吗?它用于数据库查询吗?它是否用于下载文件或访问REST API?
  2. 如何计算()从不同newThread() ?是否所有的Calculation()调用都在单个(后台)线程上,而不是每次都在一个新的(后台)线程上?
  3. 为什么在进行IO工作时调用calculation()很糟糕?
  4. 为什么在执行计算工作时调用io()不好?

Answers:


332

很好的问题,我认为文档可以提供更多细节。

  1. io()它由无限制的线程池支持,是您用于非计算密集型任务的一种东西,即不会给CPU带来太大负载的东西。因此,与文件系统的交互,与不同主机上的数据库或服务的交互都是很好的例子。
  2. computation()由有界线程池支持,该线程池的大小等于可用处理器的数量。如果您试图跨可用处理器并行调度CPU密集型工作(例如使用newThread()),那么您将承担线程创建处理器和上下文切换的开销,因为线程争夺处理器,这可能会严重影响性能。
  3. 最好computation()只进行CPU密集型工作,否则您将无法获得良好的CPU利用率。
  4. 调用io()计算工作是很糟糕的,因为2中讨论的原因io()是无限的,并且如果您io()并行调度一千个计算任务,那么这千个任务中的每一个将有各自的线程,并正在争夺CPU引起的上下文切换成本。

5
通过熟悉RxJava源码。长期以来,这一直使我感到困惑,我认为在这方面需要加强文档。
Dave Moten

2
@IgorGanapolsky我猜想这是您很少想要做的事情。为每个工作单元创建一个新线程很少会提高效率,因为线程的构造和拆卸都非常昂贵。通常,您通常想重用Calculation()和其他调度程序所做的线程。newThread()可能有合法用途(至少我能想到)的唯一时间是启动隔离的,不频繁的,长期运行的任务。即使那样,在这种情况下我也可以使用io()。
tmn

4
您可以举个例子说明trampoline()有用吗?我理解这个概念,但无法弄清楚在实际中使用它的方案。这是唯一对我来说仍然是个谜的调度程序
tmn 2015年

32
对于网络调用,请使用Schedulers.io();如果需要限制同时进行的网络调用的数量,请使用Scheduler.from(Executors.newFixedThreadPool(n))。
戴夫·莫顿

4
您可能会认为timeout默认情况下computation()会阻塞线程,但事实并非如此。在幕后computation()使用,ScheduledExecutorService因此不会延迟动作。给出这个事实computation()是一个好主意,因为如果它在另一个线程上,那么我们将承担线程切换成本。
Dave Moten

3

最重要的一点是,Scheduler.ioSchedulers.computation都由无限制线程池支持,而不是问题中提到的其他线程池。如果执行程序是使用newCachedThreadPool创建的(不受自动回收线程池的限制),则只有Schedulers.from(Executor)共享此特征。

正如先前的答复和网络上的多篇文章所充分解释的那样,Schedulers.ioSchedulers.computation应该谨慎使用,因为它们以其名称针对工作类型进行了优化。但是,以我的观点,它们最重要的作用是为反应流提供真正的并发性

与新人的看法相反,反应流不是固有并发的,而是固有异步和顺序的。因此,只有在I / O操作被阻止时(例如:使用阻止命令,例如Apache IOUtils FileUtils.readFileAsString (...)),才应使用Schedulers.io ),这样将冻结调用线程,直到该操作被完成。

使用Java AsynchronousFileChannel(...)之类的异步方法不会在操作期间阻塞调用线程,因此使用单独的线程毫无意义。实际上,Schedulers.io线程实际上不适合异步操作,因为它们不运行事件循环,并且永远不会调用回调。

相同的逻辑适用于数据库访问或远程API调用。不要使用Schedulers.io如果可以使用异步或反应性API进行调用,请。

返回并发。您可能无法访问异步或反应性API来异步或并发地执行I / O操作,因此唯一的选择是在单独的线程上分派多个调用。唉,无流是在其两端顺序,但好消息是,flatMap()操作人员可以在他们的核心介绍并发

必须在流结构中构建并发,通常使用flatMap()运算符。可以将此强大的运算符配置为在内部为您的flatMap()嵌入式Function <T,R> 提供多线程上下文。该上下文由诸如Scheduler.ioScheduler.computation的多线程Scheduler提供。

在RxJava2 Schedulers and Concurrency上的文章中找到更多详细信息,您将在其中找到代码示例以及有关如何顺序和同时使用Scheduler的详细说明。

希望这可以帮助,

软跳


2

这篇博客文章提供了一个很好的答案

从博客文章:

Schedulers.io()由无限制的线程池支持。它用于非CPU密集型I / O类型的工作,包括与文件系统的交互,执行网络调用,数据库交互等。此线程池旨在用于异步执行阻塞IO。

Schedulers.computation()由有界线程池支持,该线程池的大小最大为可用处理器的数量。它用于计算或CPU密集型工作,例如调整图像大小,处理大型数据集等。请注意:当分配的计算线程多于可用内核时,性能会因上下文切换和线程创建开销而降低,因为线程争抢处理者的时间。

Schedulers.newThread()为计划的每个工作单元创建一个新线程。该调度程序非常昂贵,因为每次都会产生新线程,并且不会发生重用。

Schedulers.from(执行执行器)创建并返回由指定执行者支持的自定义调度器。若要限制线程池中的并发线程数,请使用Scheduler.from(Executors.newFixedThreadPool(n))。这样可以保证如果在所有线程都被占用时安排了任务,则该任务将排队。池中的线程将一直存在,直到明确将其关闭。

主线程或AndroidSchedulers.mainThread()RxAndroid扩展库向RxJava提供了。主线程(也称为UI线程)是发生用户交互的地方。应注意不要使此线程过载,以防止不稳定的无响应UI,或者更糟的是,“应用程序无响应”(ANR)对话框。

Schedulers.single()是RxJava 2中的新增功能。该调度程序由单个线程支持,该线程按请求的顺序依次执行任务。

Schedulers.trampoline()通过参与的工作线程之一以FIFO(先进先出)的方式执行任务。在实现递归时经常使用它,以避免增长调用堆栈。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.