Scala MergeLatest的默认值
Scala MergeLatest的默认值,scala,akka,akka-stream,Scala,Akka,Akka Stream,MergeLatest的官方声明: MergeLatest emits列表,用于从某个输入流发出的每个元素,但仅在每个输入流发出至少一个元素之后 我的问题是:这能被绕过吗? 例如,我们是否可以提供一个默认值,使其在从任何输入流接收到至少一个元素后立即开始生成列表 以下是新的行为: (1,0,0) (2,0,0) (2,1,0) (2,1,1) (2,1,2) 而不是: (2,1,1) (2,1,2) 由于我需要将每个传入流的第一个列表也推送到输出流,您可以使用Source.single(0)
MergeLatest
的官方声明:
MergeLatest emits列表,用于从某个输入流发出的每个元素,但仅在每个输入流发出至少一个元素之后
我的问题是:这能被绕过吗?
例如,我们是否可以提供一个默认值,使其在从任何输入流接收到至少一个元素后立即开始生成列表
以下是新的行为:
(1,0,0)
(2,0,0)
(2,1,0)
(2,1,1)
(2,1,2)
而不是:
(2,1,1)
(2,1,2)
由于我需要将每个传入流的第一个列表也推送到输出流,您可以使用
Source.single(0).concat
发出零,然后发出流的其余部分:
def withInitialValue[A](source: Source[A, NotUsed], a: A): Source[A, NotUsed] =
Source.single(a).concat(source)
不幸的是,
mergeLatest
没有提供这样的选项。而且似乎没有任何流操作符能轻易做到这一点。一种方法是根据具体需要重新调整用途。好消息是,必要的代码更改非常简单,因为相关的代码实现是uniformfanishape
的标准GraphStage
import akka.stream.scaladsl._
import akka.stream.stage.{ GraphStage, GraphStageLogic, InHandler, OutHandler }
import akka.stream.{ Attributes, Inlet, Outlet, UniformFanInShape }
import scala.collection.immutable
object MergeLatestWithDefault {
def apply[T](inputPorts: Int, default: T, eagerComplete: Boolean = false): GraphStage[UniformFanInShape[T, List[T]]] =
new MergeLatestWithDefault[T, List[T]](inputPorts, default, eagerComplete)(_.toList)
}
final class MergeLatestWithDefault[T, M](val inputPorts: Int, val default: T, val eagerClose: Boolean)(buildElem: Array[T] => M)
extends GraphStage[UniformFanInShape[T, M]] {
require(inputPorts >= 1, "input ports must be >= 1")
val in: immutable.IndexedSeq[Inlet[T]] = Vector.tabulate(inputPorts)(i => Inlet[T]("MergeLatestWithDefault.in" + i))
val out: Outlet[M] = Outlet[M]("MergeLatestWithDefault.out")
override val shape: UniformFanInShape[T, M] = UniformFanInShape(out, in: _*)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with OutHandler {
private val activeStreams: java.util.HashSet[Int] = new java.util.HashSet[Int]()
private var runningUpstreams: Int = inputPorts
private def upstreamsClosed: Boolean = runningUpstreams == 0
private val messages: Array[Any] = Array.fill[Any](inputPorts)(default)
override def preStart(): Unit = in.foreach(tryPull)
in.zipWithIndex.foreach {
case (input, index) =>
setHandler(
input,
new InHandler {
override def onPush(): Unit = {
messages.update(index, grab(input))
activeStreams.add(index)
emit(out, buildElem(messages.asInstanceOf[Array[T]]))
tryPull(input)
}
override def onUpstreamFinish(): Unit = {
if (!eagerClose) {
runningUpstreams -= 1
if (upstreamsClosed) completeStage()
} else completeStage()
}
})
}
override def onPull(): Unit = {
var i = 0
while (i < inputPorts) {
if (!hasBeenPulled(in(i))) tryPull(in(i))
i += 1
}
}
setHandler(out, this)
}
override def toString = "MergeLatestWithDefault"
}
作为奖励,虽然
mergeLatest
仅在Akka Stream2.6
+上可用,但根据我的简短测试,此重新调整用途的代码在2.5
上似乎运行良好。这不是最优雅的解决方案,但似乎可以运行。也有可能在流动的中间抛出单个零点吗?我现在有一个与整数完全不同的源,所以不能把它放在那里。在它到达GraphDSL中的合并组件之前,我必须将它添加到流组件(而不是源组件)。
import akka.actor.ActorSystem
object CustomMerge {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("system")
val s1 = Source(1 to 3)
val s2 = Source(11 to 13).throttle(1, 50.millis)
val s3 = Source(101 to 103).throttle(1, 100.millis)
Source.combine(s1, s2, s3)(MergeLatestWithDefault[Int](_, 0)).runForeach(println)
}
}
// Output:
//
// List(1, 0, 0)
// List(1, 11, 0)
// List(1, 11, 101)
// List(2, 11, 101)
// List(2, 12, 101)
// List(3, 12, 101)
// List(3, 13, 101)
// List(3, 13, 102)
// List(3, 13, 103)