在可观察项目通过时锁定操作员链

时间:2015-10-13 16:25:06

标签: reactive-programming rx-java

我有一个Observable源和一个操作符链,可以将源转换为目标类型。通常,对于每个源项目,最多会生成一个目标。

Source -> Operator chain -> Target

运算符逻辑有点复杂,涉及使用IO调度程序进行多个异步数据库调用。我在这里省略了细节,因为它似乎没有相关性。 我看到的是新的Observables来自Source,而之前的Observable仍在被链条处理。所以它类似于某种管道。在许多情况下这可能是一件好事,但在我的情况下却不是。

所以我正在研究如何延迟源项目进入链(有效锁定它),直到前一个项目到达目标。是否有任何已知的模式?

我看到的一个丑陋的解决方案是在链的开头使用类似的东西:

zip(source, signal, (source, signal)->source)

其中signal是一个自定义observable,用于在每次链准备好接受新的源项目时将通知推送到(最初一个通知以及正在处理的项目到达链的末尾时) 但我发现它有点hacky。它可以更优雅地实现还是使用一组标准运算符?

以下是重现我不想要的行为的合成示例。 源是100ms间隔计时器。 运算符链是一个缓慢的(比源缓慢10倍)异步调用,它在Schedulers.io()上计算一个平方 目标项目实际上是一个源平方。

Subscription s = Observable.timer(100, 100, TimeUnit.MILLISECONDS)
    .doOnNext(source->System.out.println("source: " + source))
    .concatMap(source->Observable.create(subscr->{
      Schedulers.io().createWorker().schedule(()->{
        subscr.onNext(source * source);
        subscr.onCompleted();
      }, 1000, TimeUnit.MILLISECONDS);
    }))
    .doOnNext(target->System.out.println("target: " + target))
    .subscribe();
Thread.sleep(10000);
s.unsubscribe();

打印出来源和目标:

source: 0
source: 1
source: 2
source: 3
source: 4
source: 5
source: 6
source: 7
source: 8
source: 9
source: 10
source: 11
target: 0
source: 12
source: 13
source: 14
source: 15
source: 16
source: 17
source: 18
source: 19
source: 20
target: 1
source: 21
source: 22
source: 23
source: 24
source: 25
source: 26
source: 27
source: 28
source: 29
source: 30
source: 31
target: 4
source: 32
source: 33

但我想要达到的目的是:

source: 0
target: 0
source: 1
target: 1
source: 2
target: 4
...

1 个答案:

答案 0 :(得分:2)

根据您的来源类型,可以通过flatMap参数化来实现maxConcurrency = 1

Observable.interval(100, 100, TimeUnit.MILLISECONDS)
.onBackpressureBuffer()
.doOnNext(source -> System.out.println("source: " + source))
.flatMap(source -> 
     Observable.just(source)
     .map(v -> v * v)
     .delay(1, TimeUnit.SECONDS), 1)
.doOnNext(target->System.out.println("target: " + target))
.subscribe();
Thread.sleep(10000);

此解决方案涉及缓冲,但如果源热,您可能需要选择不同的背压策略。

与要求并不严格相关,但我想指出你的这种模式:

Schedulers.io().createWorker().schedule(()->{
    subscr.onNext(source * source);
    subscr.onCompleted();
  }, 1000, TimeUnit.MILLISECONDS);

泄漏了worker,并将使用不可重用的线程填满你的系统。如果你真的想通过˙Worker`延迟事件,你应该捕获并取消订阅工人实例:

Scheduler.Worker w = Schedulers.io().createWorker();
subscr.add(w);
w.schedule(() -> {
    try {
        subscr.onNext(source * source);
        subscr.onCompleted();
    } finally {
        w.unsubscribe();
    }
}, 1000, TimeUnit.MILLISECONDS);