2016-01-15 14 views
12

In Scala, posso usare Await per attendere il completamento di un futuro. Tuttavia, se ho registrato un callback per l'esecuzione al termine di tale futuro, come posso aspettare non solo il completamento del futuro ma anche il completamento di tale callback?Come posso attendere il completamento della callback onSuccess di Scala future?

Ecco un programma minimo, ma completo per illustrare il problema:

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

mi aspetto l'uscita di essere:

The program waited patiently for this callback to finish. 

Invece, non c'è uscita; il programma esce prima che la richiamata termini.

Si prega di notare che questo non è lo stesso problema di un completamento in attesa, a cui è stata data risposta in precedenza allo this question.

risposta

16

Non utilizzare una richiamata onSuccess, ma invece eseguire l'effetto collaterale in una chiamata Future.map. In questo modo, hai un futuro [unità] da utilizzare Attesa.

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

Si noti che se si desidera eseguire un effetto collaterale solo in caso di successo (come nel tuo esempio), mappa è appropriato. Se si desidera eseguire un effetto collaterale anche in caso di errore, e quindi è il metodo giusto da utilizzare. Vedi questo post da Roland Kuhn su scala-utente.

Inoltre, si prega non lo fanno uso Thread.sleep da nessuna parte vicino codice di produzione.

+1

Non v'è alcun punto nel fare 2 dei futures, se si sta gettando via il valore del primo. Potrebbe anche funzionare tutto in un unico futuro. –

+0

Questo doveva rimanere il più vicino possibile al codice dato. In un'applicazione reale, il primo futuro produrrebbe un valore effettivamente utilizzato. –

+0

Se 'map' e' flatMap' ottengono le stesse funzioni di 'onSuccess' (e anche di più, dato che possono restituire valori), perché il' onSuccess' nell'API? È solo per simmetria con 'onFailure'? Oppure i costrutti di livello inferiore 'onSuccess' e' onFailure' su cui 'map' e' flatMap' sono implementati sotto il cofano? –

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

stampe:

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

Uso promesse è ancora più chiaro:

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

stampe:

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

Penso che le promesse siano un'API di basso livello che non dovrebbe essere utilizzata se può essere evitata. Quindi il primo esempio che usa andThen è migliore. –

+0

Esattamente. Promise è ciò che 'andThen' usa sotto il cofano per eseguire la sincronizzazione del gestore' onComplete'. – Suma

+0

Nell'esempio con 'Promise', se si dorme prima di stampare' Questo programma ... 'non stampa mai. Penso che l'esempio abbia lo stesso problema della domanda originale: non c'è niente che aspetti 'p.future.onSuccess'. – nedim