2015-09-03 14 views
5

Angesichts der folgenden Methoden ...Zum Verständnis: wie Futures sequentiell

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

... und die folgenden für Verständnis auszuführen:

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

Die drei parallel laufen Methoden, aber in meinem Fall doSomething2 muss laufen, nachdem doSomething1 beendet ist. Wie führe ich die drei Methoden nacheinander aus?

EDIT

Wie Philosophus42 vorgeschlagen, ist hier unten eine mögliche Implementierung 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 Anruf an eine andere Methode erstellt.

EDIT 2

Vielleicht vereinfacht ich den Anwendungsfall zu viel ... 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 

Was ich versuche zu tun, ein Protokoll für jede Transaktion mit einem Auftrag zugeordnet erstellen. logService.insert wird oft aufgerufen, auch wenn transactions nur einen Eintrag enthält.

+4

Haben Sie ein Codebeispiel, das fehlschlägt? Weil die zum Verständnis die Futures tatsächlich in Sequenzen ausführen sollen, wenn sie nicht außerhalb des for {...} instanziiert werden. – Philosophus42

+0

der 'Future' wird durch einen internen Aufruf einer anderen Methode erstellt ... siehe meinen aktualisierten Beitrag. – j3d

+0

j3d: Welche Methoden genau willst du ** nicht ** parallel ausführen? Ich bin irritiert, da man hauptsächlich von der Anzahl der Aufrufe von 'logService.insert' spricht. Bitte versuchen Sie einen Arbeitsauszug zu geben und/oder geben Sie mehr Informationen darüber, was die Methoden genau machen. –

Antwort

7

Kommentar zu Ihrem Beitrag

Erstens: Wie funktioniert der Code innerhalb doSomethingX Blick mögen? Noch irrter ist es, dass mit Ihrem gegebenen Code die Futures parallel laufen.

Antwort

Um die Future Ausführung sequentieller zu machen, verwenden Sie einfach

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

Der Grund dies funktioniert, ist, dass die Aussage Zukunft {..} ..body startet asynchrone Berechnung, bei der Zeitpunkt, zu dem die Aussage ausgewertet wird.

Mit dem oben für Verständnis zu

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

es offensichtlich ist, dass

  • wenn Future{ ...block1... } es ist Ergebnis zur Verfügung hat,
  • die flatMap Methode ausgelöst wird, die
  • dann löst die Ausführung von Future { ...block2... } aus.

So wird Future { ...block2... }nachFuture { ...block1... }

Zusätzliche Informationen

A Future

Future { 
    <block> 
} 

ausgeführt sofort löst Ausführung enthaltenen Block über den ExecutionContext

Snippet 1:

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

Die beiden Berechnungen laufen parallel (im Falle Ihrer ExecutionContext ist dieser Einrichtung haben), da die beiden Werte sofort ausgewertet werden.

Snippet 2:

Das Konstrukt

def f1 = Future { ..... } 

wird die Ausführung der Zukunft starten, sobald f1 genannt wird

Edit:

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

Hier ist ein Code-Snippet, das beweist, dass computeSomething2 nach computeSomething1

Import scala.concurrent ausgeführt wird. {Await, Zukunft} Import 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 Ausgang

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

Edit 2

Wenn Sie wollen, in parallel ausgeführt werden, die Futures vorher schaffen (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}") 
} 
+0

Vielen Dank (siehe auch meinen aktualisierten Post für ein Beispiel, wie "doSomething1" aussehen könnte). Nur eine letzte Frage über die Leistung ... gibt es einen bemerkenswerten Aufwand bei der Weitergabe einer Methode, die eine "Zukunft" in "Future.apply" zurückbringt, wie von Ihnen vorgeschlagen? – j3d

+0

@ j3d: Siehe den neuen Teil in meiner Antwort. –

+0

Siehe mein zweites Update zu meinem Beitrag ... Ich habe ein Beispiel hinzugefügt, das meinem realen Anwendungsfall sehr nahe kommt. – j3d

0

Ich sehe zwei Varianten dieses Ziel zu erreichen:

Erstens: Stellen Sie sicher, dass die Futures innerhalb der für das Verständnis geschaffen 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.

Zweitens: Über die Karte Funktion der Zukunft, die Sie ausführen müssen, bevor die anderen beginnen:

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