在akka-stream中如何从期货集合中创建无序的来源

Tva*_*roh 9 scala future reactive-streams akka-stream

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

例如,有一组期货返回整数,

val f1: Future[Int] = ???
val f2: Future[Int] = ???
val fN: Future[Int] = ???
val futures = List(f1, f2, fN)
Run Code Online (Sandbox Code Playgroud)

如何创建一个

val source: Source[Int, Unit] = ???
Run Code Online (Sandbox Code Playgroud)

从中.

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

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

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

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

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

Source(futures).mapAsync(1)(identity)
Run Code Online (Sandbox Code Playgroud)

更新:这是基于@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"
  }
}
Run Code Online (Sandbox Code Playgroud)

Vik*_*ang 6

创建Futures的来源,然后通过mapAsync"展平"它:

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


kir*_*uku 5

提供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")
}
Run Code Online (Sandbox Code Playgroud)

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