J'espérais que le code comme suit attendrait les deux futurs, mais ce n'est pas le cas.
object Fiddle {
val f1 = Future {
throw new Throwable("baaa") // emulating a future that bumped into an exception
}
val f2 = Future {
Thread.sleep(3000L) // emulating a future that takes a bit longer to complete
2
}
val lf = List(f1, f2) // in the general case, this would be a dynamically sized list
val seq = Future.sequence(lf)
seq.onComplete {
_ => lf.foreach(f => println(f.isCompleted))
}
}
val a = FuturesSequence
Je supposais seq.onComplete
attendrait qu'ils se terminent tous avant de s'achever, mais pas ainsi; il en résulte:
true
false
.sequence
était un peu difficile à suivre dans la source de scala.concurrent.Future, je me demande comment j'implémenterais un parallèle qui attend tous les futurs originaux d'une séquence (de taille dynamique), ou quel pourrait être le problème ici.
Modifier: Une question connexe: https://worldbuilding.stackexchange.com/questions/12348/how-do-you-prove- vous êtes du futur :)
Une approche courante pour attendre tous les résultats (échoués ou non) consiste à "élever" les échecs dans une nouvelle représentation à l'intérieur du futur, de sorte que tous les futurs se terminent avec un certain résultat (bien qu'ils puissent se terminer avec un résultat qui représente un échec). Une façon naturelle d'obtenir cela est de passer à un Try
.
implémentation de futures de Twitter fournit une méthode liftToTry
qui rend cela trivial, mais vous pouvez faire quelque chose de similaire avec l'implémentation de la bibliothèque standard:
import scala.util.{ Failure, Success, Try }
val lifted: List[Future[Try[Int]]] = List(f1, f2).map(
_.map(Success(_)).recover { case t => Failure(t) }
)
Maintenant, Future.sequence(lifted)
sera terminée lorsque tous les futurs seront terminés, et représentera les succès et les échecs en utilisant Try
.
Et donc, une solution générique pour attendre tous les futurs originaux d'une séquence de futurs peut ressembler à ceci, en supposant qu'un contexte d'exécution est bien sûr implicitement disponible.
import scala.util.{ Failure, Success, Try }
private def lift[T](futures: Seq[Future[T]]) =
futures.map(_.map { Success(_) }.recover { case t => Failure(t) })
def waitAll[T](futures: Seq[Future[T]]) =
Future.sequence(lift(futures)) // having neutralized exception completions through the lifting, .sequence can now be used
waitAll(SeqOfFutures).map {
// do whatever with the completed futures
}
Un Future
produit par Future.sequence
se termine lorsque:
Le deuxième point est ce qui se passe dans votre cas, et il est logique de terminer dès que l'un des Future
encapsulés a échoué, car l'habillage Future
ne peut contenir qu'un seul Throwable
dans le cas d'échec. Inutile d'attendre les autres futurs car le résultat sera le même échec.
Ceci est un exemple qui prend en charge la réponse précédente. Il existe un moyen simple de le faire en utilisant uniquement les API standard Scala.
Dans l'exemple, je crée 3 futures. Celles-ci se termineront respectivement à 5, 7 et 9 secondes. L'appel à Await.result
Se bloquera jusqu'à ce que tous les contrats à terme soient résolus. Une fois les 3 futures terminés, a
sera réglé sur List(5,7,9)
et l'exécution continuera.
De plus, si une exception est levée dans l'un des futurs, Await.result
Débloquera immédiatement et lèvera l'exception. Décommentez la ligne Exception(...)
pour voir cela en action.
try {
val a = Await.result(Future.sequence(Seq(
Future({
blocking {
Thread.sleep(5000)
}
System.err.println("A")
5
}),
Future({
blocking {
Thread.sleep(7000)
}
System.err.println("B")
7
//throw new Exception("Ha!")
}),
Future({
blocking {
Thread.sleep(9000)
}
System.err.println("C")
9
}))),
Duration("100 sec"))
System.err.println(a)
} catch {
case e: Exception ⇒
e.printStackTrace()
}
Nous pouvons enrichir Seq[Future[T]]
avec sa propre méthode onComplete
via une classe implicite:
def lift[T](f: Future[T])(implicit ec: ExecutionContext): Future[Try[T]] =
f map { Success(_) } recover { case e => Failure(e) }
def lift[T](fs: Seq[Future[T]])(implicit ec: ExecutionContext): Seq[Future[Try[T]]] =
fs map { lift(_) }
implicit class RichSeqFuture[+T](val fs: Seq[Future[T]]) extends AnyVal {
def onComplete[U](f: Seq[Try[T]] => U)(implicit ec: ExecutionContext) = {
Future.sequence(lift(fs)) onComplete {
case Success(s) => f(s)
case Failure(e) => throw e // will never happen, because of the Try lifting
}
}
}
Ensuite, dans votre MWE particulier, vous pouvez faire:
val f1 = Future {
throw new Throwable("baaa") // emulating a future that bumped into an exception
}
val f2 = Future {
Thread.sleep(3000L) // emulating a future that takes a bit longer to complete
2
}
val lf = List(f1, f2)
lf onComplete { _ map {
case Success(v) => ???
case Failure(e) => ???
}}
Cette solution a l'avantage de vous permettre d'appeler un onComplete
sur une séquence de futures comme vous le feriez sur un seul futur.