我在vertx中继承了一个可观察的链,其中:
它看起来像这样:
Scheduler defaultScheduler = RxHelper.scheduler(vertx);
Scheduler blockingScheduler = RxHelper.blockingScheduler(vertx)
Observable.interval(/* some time */)
.observeOn(blockingScheduler)
.flatMapIterable(t -> getRecords())
.observeOn(defaultScheduler)
.flatMap(record -> Observable.just(record)
.flatMap(getRecordDetails(record))
.observeOn(blockingScheduler)
.doOnNext(sendRecordDetails(detail))
.observeOn(defaultScheduler)
.doOnNext(markDetailAsSeenInLocalCache(detail))
.onErrorReturn(e -> false)
.all(success -> success)))
.doOnNext(persistLocalCacheToJsonFileOnDisk())
.subscribe( m -> {/* nothing */}, e -> {/* handle fatal error */});
我的问题是,如果在getRecords()
的最后一次调用N之前调用sendRecordDetails()
进行迭代N + 1,那么我们就有竞争条件。
我对Rx / vertx中的调度没有专业的了解,但是我认为,如果这些全部放在defaultScheduler
上,那么可能可以。但是,当我们想进行异步阻塞IO时想知道阻塞调度程序时,这放弃了defaultScheduler
使用的主事件循环,这意味着它将消失并开始为第一个interval
中的下一个事件提供服务可观察的。
在最坏的情况下,如果迭代N sendRecordDetails()
长时间挂起,则可能导致迭代N + 1在迭代N之前完成。
我必须承认我的第一个直觉是“也许我们不应该为此使用Rx”。但是我已经继承了这段代码,因此我至少需要尝试看看是否有一种获得保证的惯用方法,然后才能断言这是一个丢失的原因,需要重写为其他样式。
我想到的最接近的想法是将订阅写入并发队列,并用zipWith(interval(/* some time */, observableWhichEmitsItemsFromConcurrentQueue)
代替初始间隔。这似乎有点像我在强迫框架执行不应执行的操作。另外,看起来我必须自己实现可观察到的并发队列。
所以我的问题是:强迫一个可观察的对象在事件N通知用户之前不发出事件N + 1的最合理方法是,还是强迫框架做一些本不该做的事情干嘛?
编辑:
这是一个迫使事件在发出下一个事件之前到达订阅者的示例。没有主题的序列是A,A1,A,A1,B,C,B,C
,有主题的序列是A,A1,B,C,A,A1,B,C
。我可以正确使用Subject(包括序列化)吗?
@Test
public void baz() throws InterruptedException {
BlockingDeque<Object> blockingDequed = new LinkedBlockingDeque<>();
Vertx vertx = Vertx.vertx();
Scheduler d = RxHelper.scheduler(vertx);
Scheduler b = RxHelper.blockingScheduler(vertx);
X x = new X();
Subject<Object> publishSubject = PublishSubject.create();
int blockTime = 1;
Observable.zip(publishSubject.serialize(), Observable.range(1,2), (i, j) -> j)
.observeOn(d)
.doOnNext(i -> logWithName(i, "A"))
.doOnNext(i -> block(blockTime)) //blocks for blockTime seconds
.doOnNext(i -> logWithName(i, "A1"))
.concatMap( j -> Observable.just(j).observeOn(d)
.doOnNext(i -> block(blockTime))
.doOnNext(i -> logWithName(i, "B"))
)
.observeOn(d)
.doOnNext(i -> logWithName(i, "C"))
.doOnNext(i -> publishSubject.onNext(new Object()))
.doOnComplete(() -> blockingDequed.add(new Object()))
.subscribe();
publishSubject.onNext(new Object());
blockingDequed.poll(1, TimeUnit.DAYS);
}