Reactive programming 当可观察到的项目通过操作员链时,锁定操作员链

Reactive programming 当可观察到的项目通过操作员链时,锁定操作员链,reactive-programming,rx-java,Reactive Programming,Rx Java,我有一个可观察的源和一个将源转换为目标类型的操作符链。通常,对于每个源项,最多生成一个目标 Source -> Operator chain -> Target 运算符逻辑有点复杂,使用IO调度器涉及多个异步数据库调用。我省略了这里的细节,因为它似乎不相关。 我看到的是,新的观测值来自源,而先前的观测值仍在被链处理。所以它类似于某种管道。这在很多情况下可能是件好事,但在我的情况下不是 因此,我正在寻找一种方法来延迟源项目进入链(有效地锁定它),直到前一个项目到达目标。有什么已知的模

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

Source -> Operator chain -> Target
运算符逻辑有点复杂,使用IO调度器涉及多个异步数据库调用。我省略了这里的细节,因为它似乎不相关。 我看到的是,新的观测值来自源,而先前的观测值仍在被链处理。所以它类似于某种管道。这在很多情况下可能是件好事,但在我的情况下不是

因此,我正在寻找一种方法来延迟源项目进入链(有效地锁定它),直到前一个项目到达目标。有什么已知的模式可以这样做吗

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

zip(source, signal, (source, signal)->source)
其中,信号是一个定制的可观察信号,用于在链准备接受新的源项时将通知推入(最初一个通知,并且当正在处理的项到达链的末端时) 但我觉得它有点粗糙。它可以更优雅地实现还是使用一组标准运算符实现

这里是一个合成的例子来重现我不想要的行为。 源为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
...

根据您的源类型,这可以通过将
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);

你们能不能写一个小例子,这样序列和运算符的类型就清楚了?通常,您可以使用
concatMap
来确保
Observable
s在
flatMap
的时间订阅一个,maxConcurrent=1。您的源是像主题一样热,还是像您的示例中那样像忽略背压的计时器一样热?当与至少一个订阅者共享时,源通常是热的(以.replay(1).refCount()结尾)Tx,在这里使用worker只是演示缓慢的响应(因此这里不是生产质量)。我将尝试您的解决方案,但我不喜欢的一件事是onBackpressureBuffer()将导致OOM,onBackpressureDrop()将导致似乎根据规范删除了最新的项目,我实际上只愿意删除旧项目,并保证最新的项目仍然存在。(我应该改用throttleLast吗?)还有
onBackpressureLatest
操作符。呵呵,显然我们使用的rxjava版本太旧了,在那里不可用。我们将尝试使用较新的rxjava和onBackpressureLatest。这很有魅力,而且这个技巧似乎是新的(我们必须升级到新的rxjava版本才能访问它).现在根据规范,flatMap(…,1)似乎与concatMap(…)完全相同.但在行为上有什么不同吗?我用concatMap尝试了您的代码,但实际上它产生了不需要的输出:源:0,源:0,目标:0,源:1,…。当一个源元素处于活动状态时,concatMap预取一个源元素,而flatMap不预取。