我正在尝试在RxJava中实现ObserveLatestOn运算符(实际上是RxScala)。
当我们有一个快速生产者和一个慢速订阅者时,这个运营商很有用,但订阅者并不关心在消费项目时丢失的任何物品。
大理石图:
--1---2---3----------5------6--7-8-9------|
--1=========>3===>---5=======>6======>9==>|
=
字符表示订阅者正在执行的长时间工作,>
字符表示刚完成的工作。正如使用的规范示例所示,想象一些需要显示的数据的生成者,以及作为订阅者的数据的屏幕渲染器。渲染需要很长时间,但我们不需要渲染屏幕上的每一步,只是最后一步非常好。
在上面的大理石图中,生产者发出信号1.订户开始处理它,并且需要很长时间。同时,生产者发出2和3,并且在此之后订户完成工作。它看到生产者发出的最后一项是3,所以它开始处理它。这很快,同时没有生产新项目,因此订户可以休息。然后,5到达,故事以同样的方式继续。
我花了好几个小时试图实现这个看似简单的操作符,但我仍然不满意。运算符的本质表明它应该是异步的,它应该在不同的调度程序上发出它的项目。但与此同时,我当然不希望工作人员占用线程而没有工作要做。
这是我到目前为止所提出的:
def observeLatestOn[T](o: Observable[T], scheduler: Scheduler): Observable[T] = {
@volatile var maybeNextItem: Option[Notification[T]] = None
@volatile var isWorkScheduled = false
val itemsQueueLock = new Object()
Observable(subscriber ⇒ {
def signalToSubscriber(materializedItem: Notification[T]): Unit = {
materializedItem match {
case OnNext(item) ⇒ subscriber onNext item
case OnError(error) ⇒ subscriber onError error
case OnCompleted ⇒ subscriber.onCompleted()
}
}
def queueItem(item: Notification[T]): Unit = {
val worker = scheduler.createWorker
val shouldScheduleWork = itemsQueueLock synchronized {
val result = !isWorkScheduled
maybeNextItem = Some(item)
isWorkScheduled = true
result
}
if (shouldScheduleWork) {
worker.scheduleRec {
val maybeNextItemToSignal = itemsQueueLock synchronized {
val result = maybeNextItem
if (result.isEmpty) {
worker.unsubscribe()
isWorkScheduled = false
}
maybeNextItem = None
result
}
maybeNextItemToSignal foreach signalToSubscriber
}
}
}
o.takeWhile(_ ⇒ !subscriber.isUnsubscribed).subscribe(
next ⇒ queueItem(OnNext(next)),
error ⇒ queueItem(OnError(error)),
() ⇒ queueItem(OnCompleted)
)
})
}
它似乎有用,但我不相信没有竞争条件或死锁。此外,我不确定解决方案是否可以更简单。我也一直在考虑另一种方法,比如
OperatorDebounceWithSelector
observeOn
和onBackpressureBuffer(1)
我也不知道如何为此编写确定性单元测试。与scheduleRec
一起使用时,TestScheduler
计划的工作不能被中断,我需要使用一个真正适用于不同线程的调度程序。我发现很难为多线程代码的竞争条件编写正确的单元测试。
所以,问题仍然存在:我的解决方案是否正确?有没有更简单,更好或更正确的方法?以及如何测试它的正确性?