我有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
中元素的顺序可能不同,结果可能与预期的不同。
答案 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容器来排队请求。