web-dev-qa-db-fra.com

Comment puis-je attendre qu'un Scala futur rappel onSuccess se termine?

Dans Scala, je peux utiliser Await pour attendre la fin d'un futur. Cependant, si j'ai enregistré un rappel à exécuter à la fin de ce futur, comment puis-je attendre non seulement que le futur se termine, mais aussi que ce rappel se termine?

Voici un programme minimal mais complet pour illustrer le problème:

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

Je m'attends à ce que la sortie soit:

The program waited patiently for this callback to finish.

Au lieu de cela, il n'y a pas de sortie; le programme se termine avant la fin du rappel.

Veuillez noter que ce n'est pas le même problème que d'attendre la fin d'un avenir, ce qui a été répondu précédemment à cette question .

14
Daniel Li

N'utilisez pas de rappel onSuccess, mais faites plutôt l'effet secondaire dans un appel Future.map. De cette façon, vous avez une Future [Unité] sur laquelle utiliser Await.

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

Notez que si vous souhaitez exécuter un effet secondaire uniquement en cas de succès (comme dans votre exemple), la carte est appropriée. Si vous souhaitez exécuter un effet secondaire également en cas d'échec, et alors c'est la bonne méthode à utiliser. Voir ceci post de Roland Kuhn sur scala-user.

Aussi, veuillez pas utiliser Thread.sleep n'importe où près du code de production.

20
Rüdiger Klaehn
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")
  }
}

impressions:

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

Utiliser des promesses est encore plus clair:

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

impressions:

F is COMPLETED
P is COMPLETED
The program waited patiently for this callback to finish.
9
yǝsʞǝla