Akka Stream从Sink返回对象

时间:2016-12-08 09:16:25

标签: scala akka akka-stream

我有SourceQueue。当我为此提供一个元素时,我希望它通过Stream,当它到达Sink时,输出返回到提供此元素的代码(类似于Sink.head返回RunnableGraph.run()来电的元素。

我如何实现这一目标?我的问题的一个简单例子是:

val source = Source.queue[String](100, OverflowStrategy.fail)
val flow = Flow[String].map(element => s"Modified $element")
val sink = Sink.ReturnTheStringSomehow
val graph = source.via(flow).to(sink).run()

val x = graph.offer("foo")
println(x) // Output should be "Modified foo"
val y = graph.offer("bar")
println(y) // Output should be "Modified bar"
val z = graph.offer("baz")
println(z) // Output should be "Modified baz"

编辑:对于我在此问题中提供的示例, Vladimir Matveev 提供了最佳答案。但是,应该注意的是,只有当元素以sink提供的相同顺序进入source时,此解决方案才有效。如果无法保证,sink中元素的顺序可能不同,结果可能与预期的不同。

2 个答案:

答案 0 :(得分:5)

我认为使用现有的原语从流中提取值(Sink.queue)更简单。这是一个例子:

val source = Source.queue[String](128, OverflowStrategy.fail)
val flow = Flow[String].map(element => s"Modified $element")
val sink = Sink.queue[String]().withAttributes(Attributes.inputBuffer(1, 1))

val (sourceQueue, sinkQueue) = source.via(flow).toMat(sink)(Keep.both).run()

def getNext: String = Await.result(sinkQueue.pull(), 1.second).get

sourceQueue.offer("foo")
println(getNext)

sourceQueue.offer("bar")
println(getNext)

sourceQueue.offer("baz")
println(getNext)

它完全符合您的要求。

请注意,为队列接收器设置inputBuffer属性可能对您的用例很重要 - 如果您不设置它,则缓冲区将为零大小且数据不会流动通过流直到您调用接收器上的pull()方法。

sinkQueue.pull()会产生Future[Option[T]],如果接收器收到元素,则会Some成功完成,如果流失败则会失败。如果流正常完成,则将使用None完成。在这个特定的例子中,我忽略了使用Option.get,但你可能想要添加自定义逻辑来处理这种情况。

答案 1 :(得分:1)

嗯,你知道如果看一下它的定义会返回offer()方法:)你可以做的是创建Source.queue[(Promise[String], String)],创建帮助函数,通过{{1将对推送到流中确保offer没有因为队列可能已满而失败,然后在流中完成承诺并使用promise的未来来捕获外部代码中的完成事件。

我这样做是为了限制从我项目的多个地方使用的外部API。

以下是在Typesafe将中心源添加到akka

之前在项目中的显示方式
offer

我意识到阻塞同步队列可能是瓶颈并且可能无限增长但是因为我的项目中的API调用仅来自其他akka流,这些流是反压的,我在import scala.concurrent.Promise import scala.concurrent.Future import java.util.concurrent.ConcurrentLinkedDeque import akka.stream.scaladsl.{Keep, Sink, Source} import akka.stream.{OverflowStrategy, QueueOfferResult} import scala.util.Success private val queue = Source.queue[(Promise[String], String)](100, OverflowStrategy.backpressure) .toMat(Sink.foreach({ case (p, param) => p.complete(Success(param.reverse)) }))(Keep.left) .run private val futureDeque = new ConcurrentLinkedDeque[Future[String]]() private def sendQueuedRequest(request: String): Future[String] = { val p = Promise[String] val offerFuture = queue.offer(p -> request) def addToQueue(future: Future[String]): Future[String] = { futureDeque.addLast(future) future.onComplete(_ => futureDeque.remove(future)) future } offerFuture.flatMap { case QueueOfferResult.Enqueued => addToQueue(p.future) }.recoverWith { case ex => val first = futureDeque.pollFirst() if (first != null) addToQueue(first.flatMap(_ => sendQueuedRequest(request))) else sendQueuedRequest(request) } } 中从来没有超过十几个项目。你的情况可能有所不同。

如果您创建了futureDeque,那么您将获得可重复使用的接收器。因此,每当您需要处理项目时,您将创建完整的图形并运行它。在这种情况下,您不需要hacky java容器来排队请求。