web-dev-qa-db-fra.com

Comment attendre que les tâches asynchrones se terminent dans scala?

Je suis conscient que mon problème peut sembler un peu complexe. Mais je vais essayer de bien m'exprimer.

J'ai cette méthode que je veux retourner un Map[String, List[String]] rempli de données.

def myFunction():Map[String, List[String]] = {

  val userMap = Map[String, String](("123456", "ASDBYYBAYGS456789"),
                                    ("54321", "HGFDSA5432"))

  //the result map to return when all data is collected and added
  val resultMap:Future[Map[String, List[String]]]

  //when this map is finished (filled) this map is set to resultMap
  val progressMap = Map[String, List[String]]()

  for(user <- userMap){

     //facebook graph API call to get posts.
     val responsePost = WS.url("async get to facebook url").get()

     responsePosts.flatMap { response => 
        val jsonBody = response.json
        val dataList = List[String]()

        for(i <-0 until 5){

           //parse the json-data to strings
           val messages = (jsonBody.\("statuses").\("data")(i).\("message"))
           val likesArray = (jsonBody.\("statuses").\("data")(i).\\("data")).flatMap(_.as[List[JsObject]])
           val likes = likesArray.length

           //Put post with likes in temporary list
           dataList ::=  ("Post: " + message.toString + " Likes: " + likes.toString)
        }  

           //facebook graph API call to get friends.
           val responseFriends = WS.url("async get to facebook url").get()

           responseFriends.map { response =>
               val jsonBody = response.json
               val friendCount = jsonBody.\("data")(0).\("friend_count").toString

               //add "Friends: xxx" to the dataList and add the new row to resultMap containig a list with post and friends.
               dataList ::= ("Friends: " + friendCount)
               progressMap += user._1 -> dataList

               //check if all users has been updated
               if(progressMap.size == userMap.size){
                  resultMap = progressMap
               }
            }
       }
    }

    //return the resultMap.
    return resultMap
 }
}

Mon code n'est peut-être pas écrit avec une syntaxe optimale.

Mais ce que je veux, c'est renvoyer ce resultMap avec des données. Mon problème est que depuis le "get to facebook url" se fait de manière asynchrone ce resultMap est retourné vide. Je ne veux pas que cela soit bien sûr vide.

Ce code dans ma méthode est ma solution jusqu'à présent. Cela ne fonctionne pas, évidemment, mais j'espère que vous pourrez voir ce que j'essaie de faire. N'hésitez pas à répondre avec vos pensées même si vous n'êtes pas sûr, cela pourrait me mettre sur la bonne voie.

17
malmling

Utilisation scala.concurrent.{Future, Promise}:

def doAsyncAction: Promise[T] = {
  val p = Promise[T]
  p success doSomeOperation
  p
}

def useResult = {
   val async = doAsyncAction;
   // The return of the below is Unit.
   async.future onSuccess {
      // do action.
   };
};

Une autre façon consiste à Await le résultat. (il s'agit d'une action de blocage).

Utilisé lorsque vous devez renvoyer le résultat

import scala.concurrent.{ ExecutionContext, ExecutionContext$, Future, Promise, Await }
import scala.concurrent.duration._

def method: Option[T] = {
   val future: Future[T] = Future {
       someAction
   }
   val response = future map {
       items => Some(items)
   } recover {
       case timeout: Java.util.concurrent.TimeoutException => None
   }
   Await.result(future, 5000 millis);
};

Veillez à exécuter le blocage des Futures dans leur propre exécuteur, sinon vous finissez par bloquer d'autres calculs parallèles.

39
flavian