Frage Zum Verständnis: Wie man Futures sequenziell ausführt


Angesichts der folgenden Methoden ...

def doSomething1: Future[Int] = { ... }
def doSomething2: Future[Int] = { ... }
def doSomething3: Future[Int] = { ... }

... und folgendes Verständnis:

for {
  x <- doSomething1
  y <- doSomething2
  z <- doSomething3
} yield x + y + z

Die drei Methoden laufen parallel, aber in meinem Fall doSomething2 MUSS nachlaufen doSomething1 hat beendet. Wie führe ich die drei Methoden nacheinander aus?

BEARBEITEN

Wie vorgeschlagen von Philosophus42, hier unten ist eine mögliche Umsetzung von doSomething1:

def doSomething1: Future[Int] = {
  // query the database for customers younger than 40;
  // `find` returns a `Future` containing the number of matches
  customerService.find(Json.obj("age" -> Json.obj("$lt" -> 40)))
}

... so die Future wird durch einen internen Aufruf einer anderen Methode erstellt.

BEARBEITEN 2

Vielleicht habe ich den Anwendungsfall zu sehr vereinfacht ... und es tut mir leid. Versuchen wir es noch einmal und gehen Sie näher an den realen Anwendungsfall heran. Hier sind die drei Methoden:

for {
  // get all the transactions generated by the exchange service
  transactions <- exchange.orderTransactions(orderId)

  //for each transaction create a log
  logs <- Future.sequence(tansactions.map { transaction =>
    for {
      // update trading order status
      _ <- orderService.findAndUpdate(transaction.orderId, "Executed")

      // create new log
      log <- logService.insert(Log(
        transactionId => transaction.id,
        orderId => transaction.orderId,
        ...
      ))
    } yield log
  })
} yield logs

Ich versuche, für jede Transaktion, die einem Auftrag zugeordnet ist, ein Protokoll anzulegen. logService.insert wird oft aufgerufen, auch wenn transactions enthält nur einen Eintrag.


5
2017-09-03 11:14


Ursprung


Antworten:


Kommentiere deinen Beitrag

Erstens, wie funktioniert der Code darin? doSomethingX aussehen wie? Noch irrter ist es, dass mit Ihrem gegebenen Code die Futures parallel laufen.

Antworten

Um das zu machen Future Ausführung sequentiell, einfach verwenden

for {
  v1 <- Future { ..block1... } 
  v2 <- Future { ..block2... } 
} yield combine(v1, v2)

Der Grund dafür ist, dass die Anweisung Future {..body ..} die asynchrone Berechnung startet, zu diesem Zeitpunkt wird die Anweisung ausgewertet.

Mit dem oben genannten Verständnis entzogen

Future { ..block1.. }
  .flatMap( v1 => 
     Future { ..block>.. }
       .map( v2 => combine(v1,v2) )
  )

Es ist offensichtlich das

  • ob Future{ ...block1... } Ist das Ergebnis verfügbar,
  • das flatMap Methode wird ausgelöst, die
  • löst dann die Ausführung von aus Future { ...block2... }.

So Future { ...block2... } wird ausgeführt nach  Future { ...block1... }

Zusätzliche Information

EIN Future 

Future { 
  <block> 
} 

sofort löst die Ausführung des enthaltenen Blocks über die ExecutionContext

Ausschnitt 1:

val f1 = Future { <body> }
val f2 = Future { <otherbody> }

Die beiden Berechnungen laufen parallel (falls Ihre ExecutionContext ist auf diese Weise eingerichtet), da die beiden Werte sofort ausgewertet werden.

Code 2:

Das Konstrukt

def f1 = Future { ..... }

wird die Ausführung der Zukunft einmal beginnen f1 wird genannt

Bearbeiten:

j3d, ich bin immer noch verwirrt, warum dein Code nicht wie erwartet funktioniert, wenn deine Aussage richtig ist, dass die Zukunft wird innerhalb erstellt das computeSomethingX Methoden.

Hier ist ein Codeausschnitt, der beweist, dass computeSomething2 wird nach ausgeführt computeSomething1

import scala.concurrent. {Erwarte, Zukunft}     Importieren Sie scala.concurrent.duration._

object Playground {

  import scala.concurrent.ExecutionContext.Implicits.global

  def computeSomething1 : Future[Int] = {
    Future {
      for (i <- 1 to 10) {
        println("computeSomething1")
        Thread.sleep(500)
      }
      10
    }
  }

  def computeSomething2 : Future[String] = {
    Future {
      for(i <- 1 to 10) {
        println("computeSomething2")
        Thread.sleep(800)
      }
      "hello"
    }
  }

  def main(args: Array[String]) : Unit = {

    val resultFuture: Future[String] = for {
      v1 <- computeSomething1
      v2 <- computeSomething2
    } yield v2 + v1.toString

    // evil "wait" for result

    val result = Await.result(resultFuture, Duration.Inf)

    println( s"Result: ${result}")
  }
}

mit Ausgabe

computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething1
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
computeSomething2
Result: hello10

Bearbeiten 2

Wenn Sie möchten, dass sie ausgeführt werden parallel, erstelle die Futures vorher (hier f1 und f2)

def main(args: Array[String]) : Unit = {
  val f1 = computeSomething1
  val f2 = computeSomething2

  val resultFuture: Future[String] = for {
    v1 <- f1
    v2 <- f2
  } yield v2 + v1.toString

  // evil "wait" for result

  val result = Await.result(resultFuture, Duration.Inf)

  println( s"Result: ${result}")
}

7
2017-09-03 12:11



Ich sehe zwei Varianten, um dies zu erreichen:

Zuerst: Stellen Sie sicher, dass die Futures innerhalb des Verständnisses erstellt werden. Dies bedeutet, dass Ihre Funktionen wie folgt definiert werden sollten: def doSomething1: Future[Int] = Future { ... }. In diesem Fall sollte das Verständnis die Futures nacheinander ausführen.

Zweite: Verwenden Sie die Kartenfunktion der Zukunft, die Sie vervollständigen müssen, bevor die anderen starten:

doSomething1.map{ i =>
  for {
  y <- doSomething2
  z <- doSomething3
  } yield i + y + z
}

0
2017-09-03 11:42