为什么`collect`使观察者没有收到通知?

时间:2015-11-20 10:25:11

标签: reactive-programming rx-scala

我正在使用rxscala并发现了一个非常微妙的问题,我的代码简化为以下内容:

import rx.lang.scala.Observable
import rx.lang.scala.subjects.PublishSubject

object SubtleBug extends App {

  case class Projects(projects: List[Project] = Nil)
  case class Project(name: String, docs: List[Doc] = Nil)
  case class Doc(path: String, baseContent: String)

  sealed trait ServerEvent
  case class ProjectNames(projects: Seq[String]) extends ServerEvent
  case class NewDocument(projectName: String, path: String, version: Int, content: String) extends ServerEvent

  val receivedEvents = PublishSubject[ServerEvent]

  new Thread(new Runnable {
    override def run(): Unit = {
      val events = Seq(
        new ProjectNames(Seq("p1", "p2")),
        NewDocument("p1", "/aaa", 1, "my-content"),
        NewDocument("p1", "/bbb", 1, "my-content")
      )
      events.foreach { event =>
        receivedEvents.onNext(event)
        Thread.sleep(200)
      }
    }
  }).start()

  lazy val projects: Observable[Option[Projects]] = receivedEvents.scan(Option.empty[Projects]) {
    case (_, ProjectNames(names)) => {
      Some(Projects(names.map(name => Project(name)).toList))
    }
    case (Some(ps), NewDocument(projectName, docPath, version, content)) => {
      val doc = Doc(docPath, content)
      val newPs = ps.copy(projects = ps.projects.map {
        case project if project.name == projectName => project.copy(docs = project.docs ::: List(doc))
        case p => p
      })
      Some(newPs)
    }
    case _ => None
  }.collect({
    case Some(p) => Some(p)
  })

  projects.foreach(ps => {
    println("### 111: " + ps.map(_.projects))
    projects.foreach(x => println("### 222: " + ps.map(_.projects))) // !!!(2)
  })

  Thread.sleep(2000)

}

关键点是!!!(2)行,它在projects本身内。

它打印以下内容:

### 111: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content), Doc(/bbb,my-content))), Project(p2,List())))

问题是没有### 222:行!

但是,如果我更改collect部分,并添加None案例:

.collect({
  case Some(p) => Some(p)
  case None => None // added case
})

它会按照我的预期打印### 222:行:

### 111: None
### 111: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 222: None
### 222: None
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 111: Some(List(Project(p1,List(Doc(/aaa,my-content), Doc(/bbb,my-content))), Project(p2,List())))
### 222: None
### 222: Some(List(Project(p1,List()), Project(p2,List())))
### 222: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))
### 222: Some(List(Project(p1,List(Doc(/aaa,my-content))), Project(p2,List())))

我无法理解为什么。

PS:您可以在此处克隆代码:https://github.com/freewind/rxscala-test/blob/master/src/main/scala/myrx/SubtleBug.scala

1 个答案:

答案 0 :(得分:0)

您的代码中存在竞争条件。 PublishSubject会在您订阅之前删除任何事件。因此,如果receivedEvents.onNext(event)projects.foreach之前运行,则会删除事件。在scan中,如果第一个事件被删除,模式匹配将不再有效。

您可以使用ReplaySubject进行修复。