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)
}
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.
Je viens de créer une classe TimeoutFuture
pour un collègue:
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
}
}
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)
}
ExecutionContext
qui peut ne pas être idéale.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
}
}
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))
}
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.
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
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.
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.
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)
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}"))
}
}
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)
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
}
}