RxJava 3 新不同 - 3

584 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Flowable.publish pause

Flowable.publish的实现持有一个内部队列以支持来自下游的背压支持. 在2.x中, 如果所有产生的"ConnectableFlowable"消费者都被取消, 那么这个队列, 以及因此而产生的上游源, 正在缓慢地自行耗尽. 当缺乏消费者只是暂时的时候, 这导致了意外的数据损失.

有了3.x, 该实现能够暂停, 并且已经处于内部队列的数据项将会对稍后订阅的消费者即时可用.

publish-pause 示例

ConnectableFlowable<Integer> connectable = Flowable.range(1, 200).publish();

connectable.connect();

// the first consumer takes only 50 items and cancels
connectable.take(50).test().assertValueCount(50);

// with 3.x, the remaining items will be still available
connectable.test().assertValueCount(150);

Processor.offer 空检测

使用null参数调用PublishProcessor.offer()BehaviorProcessor.offer()MulticastProcessor.offer现在将抛出NullPointerException而非通过onError进行通知并由此通知Processor. 这现在匹配了响应流规范要求的onNext方法.

offer-示例

PublishProcessor<Integer> pp = PublishProcessor.create();

TestSubscriber<Integer> ts = pp.test();

try {
   pp.offer(null);
} catch (NullPointerException expected) {
}

// no error received
ts.assertEmpty();

pp.offer(1);

// consumers are still there to receive proper items
ts.asssertValuesOnly(1);

MulticastProcessor.offer 聚合检测

MulticastProcessor被设计成搭配背压的处理器, 就像Flowable.publish操作符做的那样. 它包括内部优化, 例如将其订阅到正确类型的源时的运算符聚合.

因为用户可能保留Processor本身的引用, 因此从概念上讲, 他们可以调用onXXX方法并可能引起麻烦. 这和offer是一样的, 在先前提到的聚合发生的时候, offer方法被调用, 将引起2.x中未定义的行为.

有了3.x, offer方法将会抛出IllegalStateException并且不会打扰Processor内部的状态.

groupBy中的组抛弃

groupBy操作符是一种特殊的操作符,它将响应源作为其主要输出信号,用户也可以订阅这些内部源. 因此, 如果主序列被取消了(比如Flowable<GroupedFlowable<T>>本身), 消费者应该依然持续接收所在组的数据, 但却不会有新的组产生. 如果所有内部消费者已经取消了, 初始源因此只能被取消.

然而, 在2.x中, 没有任何机制强制内部源的消费, 由此组可能仅仅被全部忽略, 而且阻止了初始源的取消并且可能导致源泄露.

有了3.x, groupBy已经发生了变更, 由此当它发射了一个组时, 下游必须同步订阅它. 否则, 该组将被认为"被抛弃"并被终止. 通过这种方式, 被抛弃的组将不能阻止初始源的取消. 如果后来的消费者依然订阅该组, 触发组创建的数据项将依然可用.

同步订阅意味着下面的流设置将导致组抛弃及由此可能的组重建:

groupBy抛弃 示例

// observeOn creates a time gap between group emission
// and subscription
source.groupBy(v -> v)
.observeOn(Schedulers.computation())
.flatMap(g -> g)

// subscribeOn creates a time gap too
source.groupBy(v -> v)
.flatMap(g -> g.subscribeOn(Schedulers.computation()))

因此组本质上是热源, 由此应该使用observeOn将数据项的处理安全地移动到其它线程上:

source.groupBy(v -> v)
.flatMap(g -> 
    g.observeOn(Schedulers.computation())
    .map(v -> v + 1)
)

groupBy中的背压

操作符Flowable.groupBy在某种程度上甚至更加的特殊, 它协调了来自内部组的消费者的背压和来自源Flowable的请求. 复杂的是, 这样的请求可能会导致创建一个新组, 为自己请求的组创建一个新项, 或者为完全不同的组创建新项. 由此, 组可以影响彼此接收数据项的能力, 能够挂起序列, 尤其是如果有些组完全不能被消费的时候.

当通过flatMap合并组时,可能会发生后者,其中单个组的数量大于flatMap的并发级别(默认128),因此新组将无法订阅,旧组为腾出空间可能无法完成。有了concatMap,同样的问题可以立即显现.

因为RxJava是非阻塞式的, 这些静默的挂起很难探测和诊断(例如, 没有线程在groupBy或者flatMap中阻塞). 由此, 3.x改变了groupBy的行为, 之后如果直接下游不能接收新组, 序列会以MissingBackpressureException终止:

groupBy 背压示例

Flowable.range(1, 1000)
.groupBy(v -> v)
.flatMap(v -> v, 16)
.test()
.assertError(MissingBackpressureException);

而且错误消息也将展示组序:

Unable to emit a new group (#16) due to lack of requests. Please make sure the downstream can always accept a new group and each group is consumed for the whole operator to be able to proceed.

将并发水平提升到恰当的数目(或者在预先不知道组数的情况下置为Integer.MAX_VALUE)应该解决了这个问题:

.flatMap(v -> v, 1000)

引用

RxJava 3 新不同 - 1

RxJava 3 新不同 - 2

RxJava 3 新不同 - 3

RxJava 3 新不同 - 4

RxJava 3 新不同 - 5

RxJava 3 新不同 - 6

RxJava 3 新不同 - 7

RxJava 3 新不同 - 8