Scala 在akka stream中,如何从futures集合创建无序源

Scala 在akka stream中,如何从futures集合创建无序源,scala,future,akka-stream,reactive-streams,Scala,Future,Akka Stream,Reactive Streams,我需要从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.sequenceco

我需要从
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
combinator,因为从源代码获取任何信息之前,我会等待每个未来完成。我希望在任何未来完成后,以任何顺序获得结果

我知道
Source
是一个纯粹的函数式API,在具体化之前不应该运行任何东西。因此,我的想法是使用
迭代器
(它是惰性的)来创建源代码:

Source { () =>
  new Iterator[Future[Int]] {
    override def hasNext: Boolean = ???
    override def next(): Future[Int] = ???
  }
}
但这将是未来的来源,而不是实际价值的来源。我还可以使用
Await.result(future)
next
上阻塞,但我不确定哪个线程池的线程将被阻塞。这也将按顺序调用futures,而我需要并行执行

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

更新:以下是我根据@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"
  }
}

提供源的最简单方法之一是通过参与者:

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")
}

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

创建一个未来源,然后通过mapAsync“展平”它:

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

顺便问一下,为什么第一个
actorRef
参数是
0
?这有关系吗?如果使用者可以从源中取出所有元素,那么您肯定不需要缓存,因此它是0。我尝试过,但0不起作用(引发异常)。与期货集合大小相等的大小工作正常。如果我的期货是ot类型
Future[Source[T,Unit]]
-我可以做一些比
Source(futures).mapsyncUnordered(1)(identity).flatten(flattestrategy.concat)
更好的事情吗?我希望扁平化是无序的,并且还支持并行级别。我目前(只要我找到一两个小时)正在处理
flatten(flattestrategy.merge)
,这将满足您的需要。同时,您可以使用
mapsyncUnordered(par)(identity)
+FlexiMerge实现?Viktor,我没有看FlexiMerge,将尝试一下。非常感谢。
scala> Source(List(f1,f2,fN)).mapAsync(1)(identity)
res0: akka.stream.scaladsl.Source[Int,Unit] = akka.stream.scaladsl.Source@3e10d804