web-dev-qa-db-fra.com

Comment créer une source pouvant recevoir des éléments ultérieurement via un appel de méthode?

Je voudrais créer un Source et plus tard des éléments Push dessus, comme dans:

val src = ... // create the Source here
// and then, do something like this
pushElement(x1, src)
pushElement(x2, src)

Quelle est la manière recommandée de procéder?

Merci!

50
ale64bit

Il y a trois façons d'y parvenir:

1. Post-matérialisation avec SourceQueue

Vous pouvez utiliser Source.queue qui matérialise le Flow en SourceQueue:

case class Weather(zipCode : String, temperature : Double, raining : Boolean)

val bufferSize = 100

//if the buffer fills up then this strategy drops the oldest elements
//upon the arrival of a new element.
val overflowStrategy = akka.stream.OverflowStrategy.dropHead

val queue = Source.queue(bufferSize, overflowStrategy)
                  .filter(!_.raining)
                  .to(Sink foreach println)
                  .run() // in order to "keep" the queue Materialized value instead of the Sink's

queue offer Weather("02139", 32.0, true)

2. Post-matérialisation avec acteur

Il y a une question et une réponse similaire ici , l'essentiel étant que vous matérialisiez le flux en tant que ActorRef et envoyiez des messages à cette référence:

val ref = Source.actorRef[Weather](Int.MaxValue, fail)
                .filter(!_.raining)
                .to(Sink foreach println )
                .run() // in order to "keep" the ref Materialized value instead of the Sink's

ref ! Weather("02139", 32.0, true)

3. Pré-matérialisation avec acteur

De même, vous pouvez créer explicitement un acteur qui contient un tampon de messages, utiliser cet acteur pour créer une source, puis envoyer ces messages d'acteur comme décrit dans la réponse ici :

object WeatherForwarder {
  def props : Props = Props[WeatherForwarder]
}

//see provided link for example definition
class WeatherForwarder extends Actor {...}

val actorRef = actorSystem actorOf WeatherForwarder.props 

//note the stream has not been instatiated yet
actorRef ! Weather("02139", 32.0, true) 

//stream already has 1 Weather value to process which is sitting in the 
//ActorRef's internal buffer
val stream = Source(ActorPublisher[Weather](actorRef)).runWith{...}
88

Après avoir joué et cherché une bonne solution à cela, je suis tombé sur cette solution qui est propre, simple et fonctionne à la fois avant et après la matérialisation. https://stackoverflow.com/a/32553913/6791842

  val (ref: ActorRef, publisher: Publisher[Int]) =
    Source.actorRef[Int](bufferSize = 1000, OverflowStrategy.fail)
      .toMat(Sink.asPublisher(true))(Keep.both).run()

  ref ! 1 //before

  val source = Source.fromPublisher(publisher)

  ref ! 2 //before
  Thread.sleep(1000)
  ref ! 3 //before

  source.runForeach(println)

  ref ! 4 //after
  Thread.sleep(1000)
  ref ! 5 //after

Sortie:

1
2
3
4
5
3
Jesse Feinman

Depuis Akka 2.5 Source a une méthode preMaterialize .

Selon le documentation , cela ressemble à la façon indiquée de faire ce que vous demandez:

Il existe des situations dans lesquelles vous avez besoin d'une valeur matérialisée Source avant que le Source ne soit connecté au reste du graphique. Ceci est particulièrement utile dans le cas de sources "alimentées par la valeur matérialisée", comme Source.queue, Source.actorRef ou Source.maybe.

Ci-dessous un exemple sur la façon dont ce serait avec un SourceQueue. Les éléments sont poussés dans la file d'attente avant et après la matérialisation, ainsi que depuis Flow:

import akka.actor.ActorSystem
import akka.stream.scaladsl._
import akka.stream.{ActorMaterializer, OverflowStrategy}

implicit val system = ActorSystem("QuickStart")
implicit val materializer = ActorMaterializer()


val sourceDecl = Source.queue[String](bufferSize = 2, OverflowStrategy.backpressure)
val (sourceMat, source) = sourceDecl.preMaterialize()

// Adding element before actual materialization
sourceMat.offer("pre materialization element")

val flow = Flow[String].map { e =>
  if(!e.contains("new")) {
    // Adding elements from within the flow
    sourceMat.offer("new element generated inside the flow")
  }
  s"Processing $e"
}

// Actually materializing with `run`
source.via(flow).to(Sink.foreach(println)).run()

// Adding element after materialization
sourceMat.offer("post materialization element")

Sortie:

Processing pre materialization element
Processing post materialization element
Processing new element generated inside the flow
Processing new element generated inside the flow
1
PetrosP