web-dev-qa-db-fra.com

Scala Futures - construit dans le délai d'attente?

il y a un aspect des contrats à terme que je ne comprends pas exactement d'après le tutoriel officiel réf. http://docs.scala-lang.org/overviews/core/futures.html

Les contrats à terme dans Scala ont-ils un mécanisme de temporisation intégré? Supposons que l'exemple ci-dessous est un fichier texte de 5 gigaoctets ... La portée implicite de "Implicits.global" finit-elle par provoquer le onFailure de se déclencher de manière non bloquante ou peut-il être défini? Et sans une temporisation par défaut, cela ne signifie-t-il pas qu'il est possible que ni le succès ni l'échec ne se déclenche? 

import scala.concurrent._
import ExecutionContext.Implicits.global

val firstOccurence: Future[Int] = future {
  val source = scala.io.Source.fromFile("myText.txt")
  source.toSeq.indexOfSlice("myKeyword")
}
firstOccurence onSuccess {
  case idx => println("The keyword first appears at position: " + idx)
}
firstOccurence onFailure {
  case t => println("Could not process file: " + t.getMessage)
}
49
LaloInDublin

Vous n'obtenez un comportement d'expiration que lorsque vous utilisez le blocage pour obtenir les résultats de la variable Future. Si vous souhaitez utiliser les rappels non bloquants onComplete, onSuccess ou onFailure, vous devrez alors lancer votre propre gestion du délai d'attente. Akka a intégré la gestion du délai d’expiration pour la messagerie requête/réponse (?) entre les acteurs, mais ne sait pas si vous souhaitez commencer à utiliser Akka. FWIW, à Akka, pour la gestion du délai d'attente, ils composent deux Futures ensemble via Future.firstCompletedOf, l'un représentant la tâche asynchrone réelle et l'autre représentant le délai d'attente. Si le délai d'attente (via une HashedWheelTimer) apparaît en premier, le rappel asynchrone échoue.

Un exemple très simplifié de rouler votre propre pourrait aller quelque chose comme ceci. Tout d'abord, un objet pour les délais d'attente de planification:

import org.jboss.netty.util.{HashedWheelTimer, TimerTask, Timeout}
import Java.util.concurrent.TimeUnit
import scala.concurrent.duration.Duration
import scala.concurrent.Promise
import Java.util.concurrent.TimeoutException

object TimeoutScheduler{
  val timer = new HashedWheelTimer(10, TimeUnit.MILLISECONDS)
  def scheduleTimeout(promise:Promise[_], after:Duration) = {
    timer.newTimeout(new TimerTask{
      def run(timeout:Timeout){              
        promise.failure(new TimeoutException("Operation timed out after " + after.toMillis + " millis"))        
      }
    }, after.toNanos, TimeUnit.NANOSECONDS)
  }
}

Ensuite, une fonction pour prendre un avenir et y ajouter un comportement de délai

import scala.concurrent.{Future, ExecutionContext, Promise}
import scala.concurrent.duration.Duration

def withTimeout[T](fut:Future[T])(implicit ec:ExecutionContext, after:Duration) = {
  val prom = Promise[T]()
  val timeout = TimeoutScheduler.scheduleTimeout(prom, after)
  val combinedFut = Future.firstCompletedOf(List(fut, prom.future))
  fut onComplete{case result => timeout.cancel()}
  combinedFut
}

Notez que la HashedWheelTimer que j'utilise ici provient de Netty.

64
cmbaxter

Je viens de créer une classe TimeoutFuture pour un collègue:

TimeoutFuture

package model

import scala.concurrent._
import scala.concurrent.duration._
import play.libs.Akka
import play.api.libs.concurrent.Execution.Implicits._

object TimeoutFuture {
  def apply[A](timeout: FiniteDuration)(block: => A): Future[A] = {

    val prom = promise[A]

    // timeout logic
    Akka.system.scheduler.scheduleOnce(timeout) {
      prom tryFailure new Java.util.concurrent.TimeoutException
    }

    // business logic
    Future { 
      prom success block
    }

    prom.future
  } 
}

Usage

val future = TimeoutFuture(10 seconds) { 
  // do stuff here
}

future onComplete {
  case Success(stuff) => // use "stuff"
  case Failure(exception) => // catch exception (either TimeoutException or an exception inside the given block)
}

Remarques:

  • Assume le jeu! cadre (mais il est assez facile de l'adapter)
  • Chaque morceau de code fonctionne dans la même ExecutionContext qui peut ne pas être idéale.
21
Pablo Fernandez

Toutes ces réponses nécessitent des dépendances supplémentaires. J'ai décidé d'écrire une version utilisant Java.util.Timer, ce qui est un moyen efficace d'exécuter une fonction dans le futur, dans ce cas, pour déclencher un délai d'attente. 

Article de blog avec plus de détails ici

En utilisant cela avec la promesse de Scala, nous pouvons créer un avenir avec un délai d'attente comme suit: 

package justinhj.concurrency

import Java.util.concurrent.TimeoutException
import Java.util.{Timer, TimerTask}

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.language.postfixOps

object FutureUtil {

  // All Future's that use futureWithTimeout will use the same Timer object
  // it is thread safe and scales to thousands of active timers
  // The true parameter ensures that timeout timers are daemon threads and do not stop
  // the program from shutting down

  val timer: Timer = new Timer(true)

  /**
    * Returns the result of the provided future within the given time or a timeout exception, whichever is first
    * This uses Java Timer which runs a single thread to handle all futureWithTimeouts and does not block like a
    * Thread.sleep would
    * @param future Caller passes a future to execute
    * @param timeout Time before we return a Timeout exception instead of future's outcome
    * @return Future[T]
    */
  def futureWithTimeout[T](future : Future[T], timeout : FiniteDuration)(implicit ec: ExecutionContext): Future[T] = {

    // Promise will be fulfilled with either the callers Future or the timer task if it times out
    val p = Promise[T]

    // and a Timer task to handle timing out

    val timerTask = new TimerTask() {
      def run() : Unit = {
            p.tryFailure(new TimeoutException())
        }
      }

    // Set the timeout to check in the future
    timer.schedule(timerTask, timeout.toMillis)

    future.map {
      a =>
        if(p.trySuccess(a)) {
          timerTask.cancel()
        }
    }
    .recover {
      case e: Exception =>
        if(p.tryFailure(e)) {
          timerTask.cancel()
        }
    }

    p.future
  }

}
15
justinhj

Le cadre de jeu contient Promise.timeout afin que vous puissiez écrire du code comme suit

private def get(): Future[Option[Boolean]] = {
  val timeoutFuture = Promise.timeout(None, Duration("1s"))
  val mayBeHaveData = Future{
    // do something
    Some(true)
  }

  // if timeout occurred then None will be result of method
  Future.firstCompletedOf(List(mayBeHaveData, timeoutFuture))
}
5
Kir

Si vous voulez que le rédacteur (détenteur de la promesse) soit celui qui contrôle la logique de dépassement de délai, utilisez akka.pattern.after , de la manière suivante:

val timeout = akka.pattern.after(10 seconds, system.scheduler)(Future.failed(new TimeoutException(s"timed out during...")))
Future.firstCompletedOf(Seq(promiseRef.future, timeout))

De cette façon, si votre logique d'achèvement de la promesse ne se réalise jamais, l'avenir de votre appelant sera toujours complété à un moment donné avec un échec.

3
galbarm

Je suis assez surpris que ce ne soit pas standard à Scala. Mes versions sont courtes et n'ont pas de dépendances

import scala.concurrent.Future

sealed class TimeoutException extends RuntimeException

object FutureTimeout {

  import scala.concurrent.ExecutionContext.Implicits.global

  implicit class FutureTimeoutLike[T](f: Future[T]) {
    def withTimeout(ms: Long): Future[T] = Future.firstCompletedOf(List(f, Future {
      Thread.sleep(ms)
      throw new TimeoutException
    }))

    lazy val withTimeout: Future[T] = withTimeout(2000) // default 2s timeout
  }

}

Exemple d'utilisation

import FutureTimeout._
Future { /* do smth */ } withTimeout
3
Raul

Personne n'a encore mentionné akka-streams. Les flux ont une méthode facile completionTimeout , et son application à un flux à source unique fonctionne comme un avenir.

Cependant, akka-streams annule également afin d’arrêter la source, c’est-à-dire qu’il signale l’expiration du délai à la source.

3
akauppi

Vous pouvez spécifier le délai d’attente lorsque vous attendez le futur:

Pour scala.concurrent.Future, la méthode result vous permet de spécifier un délai d'expiration.

Pour scala.actors.Future, Futures.awaitAll vous permet de spécifier un délai d'attente.

Je ne pense pas qu'il y ait un délai d'attente intégré dans l'exécution d'un avenir.

3
gzm0

Monix Task a timeout support

import monix.execution.Scheduler.Implicits.global
import monix.eval._
import scala.concurrent.duration._
import scala.concurrent.TimeoutException

val source = Task("Hello!").delayExecution(10.seconds)

// Triggers error if the source does not complete in 3 seconds after runOnComplete
val timedOut = source.timeout(3.seconds)

timedOut.runOnComplete(r => println(r))
//=> Failure(TimeoutException)
1
WeiChing Lin

Cette version fonctionne sans timeout

import scala.concurrent._
import scala.concurrent.duration.FiniteDuration

object TimeoutFuture {
    def apply[A](
        timeout: FiniteDuration
    )(block: => A)(implicit executor: ExecutionContext): Future[A] =
        try {
            Future { Await.result(Future { block }, timeout) }
        } catch {
            case _: TimeoutException => Future.failed(new TimeoutException(s"Timed out after ${timeout.toString}"))
        }
}
0
unveloper

Le moyen le plus simple de spécifier un délai d'expiration sur Future IMO est le mécanisme intégré de scala utilisant scala.concurrent.Await.ready. Cette méthode génère une variable TimeoutException si le délai futur prend plus longtemps que le délai d'expiration spécifié. Sinon, il retournera le futur lui-même. Voici un exemple artificiel simple

import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.duration._
val f1: Future[Int] = Future {
  Thread.sleep(1100)
  5
}

val fDoesntTimeout: Future[Int] = Await.ready(f1, 2000 milliseconds)

val f: Future[Int] = Future {
  Thread.sleep(1100)
  5
}
val fTimesOut: Future[Int] = Await.ready(f, 100 milliseconds)
0
sparker

J'utilise cette version (basée sur l'exemple de lecture ci-dessus) qui utilise le répartiteur système Akka:

object TimeoutFuture {
  def apply[A](system: ActorSystem, timeout: FiniteDuration)(block: => A): Future[A] = {
    implicit val executionContext = system.dispatcher

    val prom = Promise[A]

    // timeout logic
    system.scheduler.scheduleOnce(timeout) {
      prom tryFailure new Java.util.concurrent.TimeoutException
    }

    // business logic
    Future {
      try {
        prom success block
      } catch {
        case t: Throwable => prom tryFailure t
      }
    }

    prom.future
  }
}
0
PJ Fanning