2016-01-15 13 views
12

W Scali mogę użyć Await, aby poczekać na ukończenie przyszłości. Jeśli jednak zarejestrowałem wywołanie zwrotne, które będzie uruchamiane po zakończeniu tej przyszłości, jak mogę czekać nie tylko na zakończenie tej usługi, ale także na zakończenie tego połączenia zwrotnego?Jak mogę czekać na zakończenie wywołania zwrotnego onSuccess w Scala?

Tutaj jest minimalny, ale kompletny program do zilustrowania problemu:

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) 
    } 
} 

Spodziewam się, że wyjście będzie:

The program waited patiently for this callback to finish. 

Zamiast tego, nie ma wyjścia; program kończy działanie przed zakończeniem oddzwaniania.

Należy pamiętać, że nie jest to ten sam problem, co oczekiwanie na ułożenie w przyszłości, na co wcześniej udzielono odpowiedzi pod numerem this question.

Odpowiedz

16

Nie używaj wywołania zwrotnego onSukces, ale zamiast tego wywołuj efekt uboczny w wywołaniu Future.map. W ten sposób masz Future [Unit], aby używać Await na.

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) 
    } 
} 

Uwaga: jeśli chcesz wywołać efekt uboczny tylko w przypadku powodzenia (jak w twoim przykładzie), mapa jest odpowiednia. Jeśli chcesz wykonać efekt uboczny również w przypadku awarii, a następnie jest właściwą metodą użycia. Zobacz to post od Rolanda Kuhna na scala-user.

Również, proszę nie używać Thread.sleep w dowolnym miejscu w pobliżu kodu produkcyjnego.

+1

Nie ma sensu w podejmowaniu 2 futures jeśli rzucanie od wartości pierwszego. Równie dobrze można uruchomić wszystko w jednej przyszłości. –

+0

To miało pozostać jak najbliżej podanego kodu. W prawdziwej aplikacji pierwsza przyszłość wytworzyłaby wartość, której faktycznie używasz. –

+0

Jeśli 'map' i' flatMap' dokonują tych samych rzeczy co 'onSuccess' (i więcej, ponieważ mogą zwracać wartości), dlaczego w ogóle ma on' onSuccess' w API? Czy jest to tylko dla symetrii z 'onFailure'? Lub są konstrukcje niższego poziomu 'onSuccess' i' onFailure', w których 'map' i' flatMap' są implementowane pod maską? –

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") 
    } 
} 

drukuje:

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

Korzystanie obietnic jest jeszcze bardziej oczywiste:

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") 
    } 
} 

drukuje:

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

Myślę, że obietnice to niski poziom API, którego nie należy używać, jeśli można tego uniknąć. Pierwszy przykład użycia i jest lepszy. –

+0

Dokładnie. Obietnica polega na tym, że 'i Ten" używa pod maską synchronizacji wykonywania 'onComplete' handler. – Suma

+0

W przykładzie z 'Obietnica', jeśli śpisz przed drukowaniem' Ten program ... 'nigdy nie drukuje. Myślę, że ten przykład ma taki sam problem jak oryginalne pytanie - nic nie czeka na 'p.future.onSuccess'. – nedim