2017-06-08 2 views
0

Disons que j'ai un ListBuffer[Int] et que je l'itère avec une boucle foreach, et chaque boucle va modifier cette liste depuis l'intérieur d'un Future (en supprimant l'élément actuel), et fera quelque chose de spécial quand la liste est vide. Exemple de code:Pour boucle contenant Scala Futures modifiant une liste

import scala.concurrent.Future 
import scala.concurrent.ExecutionContext.Implicits.global 
import scala.collection.mutable.ListBuffer 

val l = ListBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 
l.foreach(n => Future { 
    println(s"Processing $n") 
    Future { 
    l -= n 
    println(s"Removed $n") 
    if (l.isEmpty) println("List is empty!") 
    } 
}) 

Ceci va probablement se terminer très mal. J'ai un code plus complexe avec une structure similaire et les mêmes besoins, mais je ne sais pas comment le structurer pour que je puisse obtenir la même fonctionnalité d'une manière plus fiable.

+0

Avez-vous besoin d'être mutable? Pourquoi avenir dans l'avenir? – mfirry

+0

@mfirry J'ai besoin d'un moyen de faire quelque chose de différent quand la liste est vide (c'est-à-dire, quand tous les éléments ont été consommés et que les actions sont terminées). Ainsi, par exemple, vérifier que le dernier élément est en cours de traitement ne fonctionnerait pas, car il pourrait y avoir d'autres éléments en cours de traitement. La partie "Future inside Future" est d'imiter la structure du code réel - il a une raison d'être comme ça, je simplifie un peu ici. –

+0

Ok. Mais avez-vous vraiment besoin de modifier le tampon? – mfirry

Répondre

0

La façon dont vous présentez votre problème n'est vraiment pas dans le paradigme fonctionnel auquel scala est destiné. Ce que vous semblez vouloir, c'est faire une liste de calculs asynchrones, faire quelque chose à la fin de chacun, et autre chose quand tout est fini. C'est assez simple si vous utilisez des suites, qui sont simples à implémenter avec les méthodes map et flatMap sur Future.

val fa: Future[Int] = Future { 1 } 
// will apply the function to the result when it becomes available 
val fb: Future[Int] = fa.map(a => a + 1) 
// will start the asynchronous computation using the result when it will become available 
val fc: Future[Int] = fa.flatMap(a => Future { a + 2 }) 

Une fois que vous avez tout cela, vous pouvez facilement faire quelque chose quand chacun de vos Future finalise (avec succès):

val myFutures: List[Future[Int]] = ??? 
myFutures.map(futInt => futInt.map(int => int + 2)) 

Ici, je vais ajouter 2 à chaque valeur que je reçois de l'autre asynchrone calculs dans le List.

Vous pouvez aussi choisir d'attendre que tous les Future s dans votre liste complète en utilisant Future.sequence:

val myFutureList: Future[List[Int]] = Future.sequence(myFutures) 

Encore une fois, vous obtenez un Future, qui sera résolu lorsque chacun des Future s à l'intérieur la liste d'entrée est résolue avec succès, ou échouera chaque fois que l'un de vos Future échouera. Vous pourrez alors utiliser map ou flatMap sur ce nouveau Future, pour utiliser toutes les valeurs calculées en même temps.

Alors, voici comment j'écrire le code proposé:

val l = 1 to 10 

val processings: Seq[Future[Unit]] = l.map {n => 
    Future(println(s"processing $n")).map {_ => 
    println(s"finished processing $n") 
    } 
} 

val processingOver: Future[Unit] = 
    Future.sequence(processings).map { (lu: Seq[Unit]) => 
    println(s"Finished processing ${lu.size} elements") 
    } 

Bien sûr, je recommande d'avoir des fonctions réelles plutôt que des procédures (retour Unit), de sorte que vous pouvez avoir des valeurs à faire quelque chose avec. J'ai utilisé println pour avoir un code qui produira le même résultat que le vôtre (à l'exception des tirages, qui ont une signification légèrement différente, puisque nous ne faisons plus de mutation).