web-dev-qa-db-fra.com

Scala - ScheduledFuture

J'essaie de mettre en œuvre l'avenir prévu à Scala. Je voudrais qu'il attende un moment précis et exécute ensuite le corps. Jusqu'à présent, j'ai essayé l'approche simple et suivante

val d = 5.seconds.fromNow

val f = future {Await.ready(Promise().future, d.timeLeft); 1}

val res = Await.result(f, Duration.Inf)

mais je reçois le TimeoutExcpetion sur l'avenir. Est-ce même la bonne approche ou dois-je simplement utiliser ScheduledExecutor de Java?

31
Bober02

Vous pouvez changer votre code en quelque chose comme ceci:

val d = 5.seconds.fromNow
val f = Future {delay(d); 1}
val res = Await.result(f, Duration.Inf)

def delay(dur:Deadline) = {
  Try(Await.ready(Promise().future, dur.timeLeft))
}

Mais je ne le recommanderais pas. Ce faisant, vous bloqueriez dans un avenir (bloquer pour attendre ce Promise qui ne se terminera jamais), et je pense que le blocage dans le ExecutionContext est fortement déconseillé. Je chercherais à utiliser l'exécuteur planifié Java comme vous l'avez indiqué) ou vous pourriez envisager d'utiliser Akka comme @ alex23 recommandé.

6
cmbaxter

Akka a akka.pattern:

def after[T](duration: FiniteDuration, using: Scheduler)(value: ⇒ Future[T])(implicit ec: ExecutionContext): Future[T]

"Renvoie un scala.concurrent.Future qui sera terminé avec le succès ou l'échec de la valeur fournie après la durée spécifiée."

http://doc.akka.io/api/akka/2.2.1/#akka.pattern.package

63
Viktor Klang

Il n'y a rien à faire dès le départ en utilisant uniquement la bibliothèque standard. Pour la plupart des cas d'utilisation simples, vous pouvez utiliser un petit assistant tel que celui-ci:

object DelayedFuture {
  import Java.util.{Timer, TimerTask}
  import Java.util.Date
  import scala.concurrent._
  import scala.concurrent.duration.FiniteDuration
  import scala.util.Try

  private val timer = new Timer(true)

  private def makeTask[T]( body: => T )( schedule: TimerTask => Unit )(implicit ctx: ExecutionContext): Future[T] = {
    val prom = Promise[T]()
    schedule(
      new TimerTask{
        def run() {
          // IMPORTANT: The timer task just starts the execution on the passed
          // ExecutionContext and is thus almost instantaneous (making it 
          // practical to use a single  Timer - hence a single background thread).
          ctx.execute( 
            new Runnable {
              def run() {
                prom.complete(Try(body))
              }
            }
          )
        }
      }
    )
    prom.future
  }
  def apply[T]( delay: Long )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
    makeTask( body )( timer.schedule( _, delay ) )
  }
  def apply[T]( date: Date )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
    makeTask( body )( timer.schedule( _, date ) )
  }
  def apply[T]( delay: FiniteDuration )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
    makeTask( body )( timer.schedule( _, delay.toMillis ) )
  }
}

Cela peut être utilisé comme ceci:

import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits._

DelayedFuture( 5 seconds )( println("Hello") )

Notez que contrairement à Java futures planifiés, cette implémentation ne vous permettra pas d'annuler le futur.

18

Si vous souhaitez planifier l'achèvement sans Akka, vous pouvez utiliser un temporisateur Java Timer pour planifier une promesse de terminer:

def delay[T](delay: Long)(block: => T): Future[T] = {
  val promise = Promise[T]()
  val t = new Timer()
  t.schedule(new TimerTask {
    override def run(): Unit = {
      promise.complete(Try(block))
    }
  }, delay)
  promise.future
}
17
Arne Claassen

Ma solution est assez similaire à celle de Régis mais j'utilise Akka pour programmer:

 def delayedFuture[T](delay: FiniteDuration)(block: => T)(implicit executor : ExecutionContext): Future[T] = {
    val promise = Promise[T]

    Akka.system.scheduler.scheduleOnce(delay) {
      try {
        val result = block
        promise.complete(Success(result))
      } catch {
        case t: Throwable => promise.failure(t)
      }
    }
    promise.future
  }
7
agabor

Toutes les autres solutions utilisent akka ou bloquent un thread par tâche retardée. Une meilleure solution (sauf si vous utilisez déjà akka) consiste à utiliser ScheduledThreadPoolExecutor de Java. Voici un exemple d'un wrapper scala pour cela:

https://Gist.github.com/platy/8f0e634c64d9fb54559c

2
MikeB