【问题标题】:In akka-stream how to create a unordered Source from a futures collection在 akka-stream 中,如何从期货集合中创建无序 Source
【发布时间】:2015-12-02 03:41:25
【问题描述】:

我需要从Future[T] 的集合中创建一个akka.stream.scaladsl.Source[T, Unit]

例如,有一个返回整数的期货集合,

val f1: Future[Int] = ???
val f2: Future[Int] = ???
val fN: Future[Int] = ???
val futures = List(f1, f2, fN)

如何创建一个

val source: Source[Int, Unit] = ???

来自它。

我不能使用Future.sequence 组合器,从那时起我会等待每个未来完成,然后再从源中获取任何东西。我想在任何未来完成后立即以任何顺序获得结果。

我知道Source 是一个纯粹的函数式 API,它不应该在以某种方式实现它之前运行任何东西。所以,我的想法是使用Iterator(这是懒惰的)来创建一个源:

Source { () =>
  new Iterator[Future[Int]] {
    override def hasNext: Boolean = ???
    override def next(): Future[Int] = ???
  }
}

但这将是期货的来源,而不是实际价值的来源。我也可以使用Await.result(future) 阻止next,但我不确定哪个线程池的线程会被阻止。这也将按顺序调用期货,而我需要并行执行。

更新 2:事实证明有一种更简单的方法(感谢 Viktor Klang):

Source(futures).mapAsync(1)(identity)

更新:这是我根据@sschaef 的回答得到的:

def futuresToSource[T](futures: Iterable[Future[T]])(implicit ec: ExecutionContext): Source[T, Unit] = {
  def run(actor: ActorRef): Unit = {
    futures.foreach { future =>
      future.onComplete {
        case Success(value) =>
          actor ! value
        case Failure(NonFatal(t)) =>
          actor ! Status.Failure(t) // to signal error
      }
    }

    Future.sequence(futures).onSuccess { case _ =>
      actor ! Status.Success(()) // to signal stream's end
    }
  }

  Source.actorRef[T](futures.size, OverflowStrategy.fail).mapMaterializedValue(run)
}

// ScalaTest tests follow

import scala.concurrent.ExecutionContext.Implicits.global

implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()

"futuresToSource" should "convert futures collection to akka-stream source" in {
  val f1 = Future(1)
  val f2 = Future(2)
  val f3 = Future(3)

  whenReady {
    futuresToSource(List(f1, f2, f3)).runFold(Seq.empty[Int])(_ :+ _)
  } { results =>
    results should contain theSameElementsAs Seq(1, 2, 3)
  }
}

it should "fail on future failure" in {
  val f1 = Future(1)
  val f2 = Future(2)
  val f3 = Future.failed(new RuntimeException("future failed"))

  whenReady {
    futuresToSource(List(f1, f2, f3)).runWith(Sink.ignore).failed
  } { t =>
    t shouldBe a [RuntimeException]
    t should have message "future failed"
  }
}

【问题讨论】:

    标签: scala future akka-stream reactive-streams


    【解决方案1】:

    创建一个 Futures 源,然后通过 mapAsync 将其“展平”:

    scala> Source(List(f1,f2,fN)).mapAsync(1)(identity)
    res0: akka.stream.scaladsl.Source[Int,Unit] = akka.stream.scaladsl.Source@3e10d804
    

    【讨论】:

    • 如果我的期货不是Future[Source[T, Unit]] 类型怎么办——我能做的比Source(futures).mapAsyncUnordered(1)(identity).flatten(FlattenStrategy.concat) 更好吗?我希望 flatten 是无序的,并且还支持并行级别。
    • 我目前(只要我找到一两个小时)在flatten(FlattenStrategy.merge) 上工作,这将满足您的需求。同时,您可以使用mapAsyncUnordered(par)(identity) + FlexiMerge 实现吗?
    • Viktor,我没看过 FlexiMerge,试试看。谢谢。
    【解决方案2】:

    提供 Source 的最简单方法之一是通过 Actor:

    import scala.concurrent.Future
    import akka.actor._
    import akka.stream._
    import akka.stream.scaladsl._
    
    implicit val system = ActorSystem("MySystem")
    
    def run(actor: ActorRef): Unit = {
      import system.dispatcher
      Future { Thread.sleep(100); actor ! 1 }
      Future { Thread.sleep(200); actor ! 2 }
      Future { Thread.sleep(300); actor ! 3 }
    }
    
    val source = Source
      .actorRef[Int](0, OverflowStrategy.fail)
      .mapMaterializedValue(ref ⇒ run(ref))
    implicit val m = ActorMaterializer()
    
    source runForeach { int ⇒
      println(s"received: $int")
    }
    

    Actor 通过Source.actorRef 方法创建,并通过mapMaterializedValue 方法提供。 run 只需获取 Actor 并将所有完成的值发送给它,然后可以通过 source 访问。在上面的示例中,值直接在 Future 中发送,但这当然可以在任何地方完成(例如在 Future 的 onComplete 调用中)。

    【讨论】:

    • 顺便说一句,为什么第一个actorRef 参数是0?有关系吗?
    • 如果消费者可以从源中取出所有元素,那么你肯定不需要缓存,因此它是 0。
    • 我试过了,但零不起作用(抛出异常)。大小等于期货集合大小工作正常。
    猜你喜欢
    • 2020-08-14
    • 2019-11-13
    • 2018-09-17
    • 1970-01-01
    • 2016-11-21
    • 2019-01-05
    • 2019-02-18
    • 1970-01-01
    • 2017-06-22
    相关资源
    最近更新 更多