2016-01-15 9 views
12

In Scala kann ich Await verwenden, um auf eine Zukunft zu warten, um abzuschließen. Wenn ich jedoch nach Abschluss dieser Zukunft einen Rückruf registriert habe, wie kann ich dann nicht nur auf die Fertigstellung der Zukunft warten, sondern auch darauf, dass dieser Rückruf beendet wird?Wie warte ich darauf, dass ein onSuccess-Rückruf von Scala future abgeschlossen wird?

Hier ist ein minimal, aber vollständiges Programm, das Problem zu veranschaulichen:

import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration.Duration 
import scala.concurrent.{ Await, Future } 

object Main { 
    def main(args: Array[String]): Unit = { 
    val f: Future[Int] = Future(0) 
    f.onSuccess { case _ => 
     Thread.sleep(10000) 
     println("The program waited patiently for this callback to finish.") 
    } 

    // This waits for `f` to complete but doesn't wait for the callback 
    // to finish running. 
    Await.ready(f, Duration.Inf) 
    } 
} 

erwarte ich, dass der Ausgang sein:

The program waited patiently for this callback to finish. 

Stattdessen gibt es keine Ausgabe; Das Programm wird beendet, bevor der Rückruf abgeschlossen ist.

Bitte beachten Sie, dass dies nicht das gleiche Problem ist wie das Warten auf eine Zukunft, die zuvor unter this question beantwortet wurde.

Antwort

16

Verwenden Sie keinen onSuccess-Rückruf, sondern den Nebeneffekt in einem Future.map-Aufruf. Auf diese Weise haben Sie eine Zukunft [Einheit], auf die Sie warten können.

import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration.Duration 
import scala.concurrent.{ Await, Future } 

object Main { 
    def main(args: Array[String]): Unit = { 
    val f: Future[Int] = Future(0) 
    val f2: Future[Unit] = f.map { x => 
     Thread.sleep(10000) 
     println("The program waited patiently for this callback to finish.") 
    } 

    Await.ready(f2, Duration.Inf) 
    } 
} 

Beachten Sie, dass, wenn Sie nur eine Nebenwirkung ausgeführt werden sollen im Erfolgsfall (wie in Ihrem Beispiel), ist Karte angebracht. Wenn Sie einen Nebeneffekt auch im Falle eines Fehlers ausführen möchten, und dann ist die richtige Methode zu verwenden. Sehen Sie diese post von Roland Kuhn auf Scala-Benutzer.

Auch bitte nicht Verwenden Sie Thread.sleep überall in der Nähe von Produktionscode.

+1

Es gibt keinen Punkt 2 Futures zu machen ist, wenn Sie werfen werden weg den Wert des ersten. Könnte auch einfach alles in einer einzigen Zukunft laufen lassen. –

+0

Dies sollte so nah wie möglich an dem angegebenen Code bleiben. In einer realen Anwendung würde die erste Zukunft einen Wert erzeugen, den Sie tatsächlich verwenden. –

+0

Wenn 'map' und' flatMap' dieselben Funktionen wie 'onSuccess' (und mehr, da sie Werte zurückgeben können) ausführen, warum sollte der 'onSuccess' in der API überhaupt sein? Ist es nur für die Symmetrie mit "onFailure"? Oder sind 'onSuccess' und' onFailure' untergeordnete Konstrukte, auf denen 'map' und' flatMap' unter der Haube implementiert sind? –

7
import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration.Duration 
import scala.concurrent.{ Await, Future } 
import scala.util._ 

object Main { 
    def main(args: Array[String]): Unit = { 
    val f1: Future[Int] = Future(0) 
    val f2 = f1 andThen { 
     case Success(v) => 
     Thread.sleep(10000) 
     println("The program waited patiently for this callback to finish.") 
     case Failure(e) => 
     println(e) 
    } 

    Await.ready(f1, Duration.Inf) 
    println("F1 is COMPLETED") 
    Await.ready(f2, Duration.Inf) 
    println("F2 is COMPLETED") 
    } 
} 

druckt:

F1 is COMPLETED 
The program waited patiently for this callback to finish. 
F2 is COMPLETED 

Mit Versprechen ist noch deutlicher:

import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration.Duration 
import scala.concurrent._ 
import scala.util._ 

object Main { 
    def main(args: Array[String]): Unit = { 
    val f: Future[Int] = Future(0) 
    val p = Promise[Unit]() 
    p.future.onSuccess { case _ => 
     println("The program waited patiently for this callback to finish.") 
    } 
    f.onSuccess { case _ => 
     Thread.sleep(10000) 
     p.success(()) 
    } 

    Await.ready(f, Duration.Inf) 
    println("F is COMPLETED") 
    Await.ready(p.future, Duration.Inf) 
    println("P is COMPLETED") 
    } 
} 

druckt:

F is COMPLETED 
P is COMPLETED 
The program waited patiently for this callback to finish. 
+1

Ich denke, Versprechen sind eine Low-Level-API, die nicht verwendet werden sollte, wenn es vermieden werden kann. Das erste Beispiel mit andThen ist also besser. –

+0

Genau. Versprechen ist, was 'andThen' unter der Haube die Synchronisierung des 'onComplete'-Handlers durchführt. – Suma

+0

In dem Beispiel mit 'Promise', wenn Sie schlafen, bevor Sie' This program ... 'drucken, wird es niemals drucken. Ich denke, das Beispiel hat das gleiche Problem wie die ursprüngliche Frage - es wartet nichts auf 'p.future.onSuccess'. – nedim

Verwandte Themen