具有延迟订阅的RX启动和停止事件流

时间:2017-10-27 10:23:33

标签: rxjs reactive-programming rx-py

我正在尝试创建一个开始和停止事件的主题,其中后期订阅者只收到未完成的开始事件。即。那些没有相应停止事件的人。

这是一些RxPY代码:

from rx.subjects import ReplaySubject

start = ReplaySubject()

start.subscribe(lambda x: print("subscriber1: " + str(x)))

start.on_next(("a", "start"))
start.on_next(("b", "start"))
start.on_next(("b", "stop"))

start.subscribe(lambda x: print("subscriber2: " + str(x)))

start.on_next(("c", "start"))

这给出了输出:

subscriber1: ('a', 'start')
subscriber1: ('b', 'start')
subscriber1: ('b', 'stop')
subscriber2: ('a', 'start')
subscriber2: ('b', 'start')
subscriber2: ('b', 'stop')
subscriber1: ('c', 'start')
subscriber2: ('c', 'start')

我希望:

subscriber1: ('a', 'start')
subscriber1: ('b', 'start')
subscriber1: ('b', 'stop')
subscriber2: ('a', 'start')
subscriber1: ('c', 'start')
subscriber2: ('c', 'start')

我认为像扫描操作符这样的东西是必需的,但不能把它放在一起。任何想法都感激不尽:)

1 个答案:

答案 0 :(得分:0)

最干净的解决方案是使用主流的副作用来更新字典并将未完成的事件合并到新订阅者。

class EventObserver(Observer):
  def __init__(self):
    self.cached_events = set()
    self.mirror = Subject() # re-emits all values

  on_next(self, value):
    self.mirror.next(value) # stream to late observers
    if(value[1] == 'stop'):
      try:
        self.cached_events.remove(value[0])
      except KeyError:
        pass
    else:
      self.cached_events.add(value[0])

  on_error(self, e):
    self.mirror.error(e) # + other error logic

  on_completed(self):
    self.mirror.complete() # + other completion logic

  late_subscribe(self, subscriber):
    return Observable.merge(
      Observable.from(list(self.cached_events)),
      self.mirror
    ).subscribe(subscriber)

使用如下:

event_observer = EventObserver()
events$.subscribe(event_observer)

# late subscription:
event_observer.late_subscribe(...)

答案的其余部分解释了为什么你可能更倾向于采用被动方法。

反应式方法:

这是我能想到的最简单的解决方案,如果您不介意迟到的订阅者等到下一个活动。正如你所看到的,它并不是最漂亮的。

pub_events$ = events$.publish(); # in case your events$ aren't hot
replay_events$ = pub_events$.replay();

# late subscription:
replay_events$.window(events$.take(1))
              .scan(lambda is_first, o: 
                      o.reduce(lambda D, x: D.update({ x[0]: x[1] == 'stop' }) or D, {})
                       .flatMap(lambda D: Observable.from([ k for k, v in D.items() if v == False ]))
                      if is_first == True else o,
                    True)
              .flatMap(lambda o: o)

目标是使用从所有先前事件的缓存构建的未完成事件的过滤列表来启动延迟订阅。最大的障碍是ReplaySubject没有将这些缓存事件与新事件区分开来。解决上述问题的第一步是window关于下一个事件,期望ReplaySubject在此之前发出缓存事件。由于您的要求听起来像是优化而非正确,因此这里的竞争条件可能不是什么大问题。

最多有两个窗口:一个缓存事件和一个新事件(如果有的话),所以scan利用Python类型的弱点来检查我们的哪个窗口如果它是缓存的事件,我们建立一个事件键字典→是否该事件是"停止"。最后一步是使用flatMap将未停止的值注入流中。