Scala Фьючерсы - встроенный тайм-аут?

существует аспект фьючерсов, который я точно не понимаю из официального учебника ref. http://docs.scala-lang.org/overviews/core/futures.html

У фьючерсов в scala есть встроенный механизм тайм-аута? Скажем, пример ниже представляет собой текстовый файл размером 5 гигабайт... неявная область действия "Implicits.global" в конечном итоге приводит к тому, что onFailure срабатывает неблокирующим способом или может быть определен? И без какого-либо дефолтного тайм-аута, не означает ли это, что ни один успех и неудача никогда не будут срабатывать?

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

Ответ 1

Вы получаете только время ожидания, когда используете блокировку для получения результатов Future. Если вы хотите использовать неблокирующие обратные вызовы onComplete, onSuccess или onFailure, тогда вам придется сворачивать собственную обработку таймаута. Akka построила обработку тайм-аута для обмена сообщениями/ответами (?) между участниками, но не уверен, хотите ли вы начать использовать Akka. FWIW, в Akka, для обработки тайм-аута, они составляют два Futures вместе через Future.firstCompletedOf, один из которых представляет собой реальную задачу асинхронного вызова, а также одну, которая представляет тайм-аут. Если таймер тайм-аута (через HashedWheelTimer) появляется первым, вы получаете отказ в обратном вызове async.

Очень упрощенный пример того, как можно перевернуть свою собственную, может выглядеть примерно так. Во-первых, объект для расписания тайм-аутов:

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

Затем функция, чтобы взять Будущее и добавить к нему время ожидания:

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
}

Обратите внимание, что HashedWheelTimer, который я использую здесь, - это Netty.

Ответ 2

Я только что создал класс TimeoutFuture для коллеги:

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

Использование

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, который может быть не идеальным.

Ответ 3

Все эти ответы требуют дополнительных зависимостей. Я решил написать версию, используя java.util.Timer, который является эффективным способом запуска функции в будущем, в данном случае для запуска тайм-аута.

Сообщение в блоге с более подробной информацией здесь

Используя это с Scala Promise, мы можем создать будущее со временем ожидания следующим образом:

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

}

Ответ 4

Структура воспроизведения содержит Promise.timeout, поэтому вы можете писать код, как показано ниже.

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

Вы можете указать время ожидания, когда вы ждете в будущем:

Для scala.concurrent.Future метод result позволяет указать тайм-аут.

Для scala.actors.Future, Futures.awaitAll позволяет указать тайм-аут.

Я не думаю, что есть таймаут, встроенный в выполнение Будущего.

Ответ 6

Я совершенно удивлен, что это не стандартно в Scala. Мои версии коротки и не имеют зависимостей

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
  }

}

Пример использования

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

Ответ 7

Никто не упомянул akka-streams. Потоки имеют простой метод completionTimeout, и применение этого в потоке с одним источником работает как будущее.

Но, akka-streams также отменяет, поэтому может фактически закончить запуск источника, то есть он сигнализирует о тайм-ауте для источника.

Ответ 8

Если вы хотите, чтобы автор (владелец обещаний) был тем, кто контролирует логику тайм-аута, используйте akka.pattern.after, в следующим образом:

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

Таким образом, если ваша логика завершения обещания никогда не состоится, ваше будущее вызывающего абонента все равно будет завершено в какой-то момент с отказом.

Ответ 9

Monix Task имеет тайм - аут поддержки

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)

Ответ 10

Я использую эту версию (основанную на примере Play выше), которая использует диспетчер системы 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
  }
}

Ответ 11

Эта версия работает без использования внешнего таймера (просто Await.result)

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

Ответ 12

Самый простой способ указать время ожидания в Future IMO - это встроенный механизм scala, использующий scala.concurrent.Await.ready Это приведет к возникновению TimeoutException если Future займет больше времени, чем указанное время ожидания. Иначе оно вернет само Будущее. Вот простой надуманный пример

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)

Ответ 13

Вы можете подождать до конца, воспользовавшись Await.

import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

val meaningOfLife: Int = Await.result(Future(42), 1.nano)
println (meaningOfLife)

Вышеуказанные отпечатки 42

Вам может понадобиться неявный ExecutionContext, в этом случае просто добавьте:

import scala.concurrent.ExecutionContext.Implicits.global

Другой способ сделать это - использовать Coeval из monix. Этот метод работает не во всех ситуациях, и вы можете прочитать все об этом здесь. Основная идея заключается в том, что иногда будущее не занимает много времени и возвращает результат синхронного вызова функции или значения, поэтому это будущее можно оценить в текущем потоке. Это также полезно для тестирования и фальсификации будущего. Кроме того, вам не нужно указывать ожидаемый тайм-аут, но все же приятно не беспокоиться об этом.

Вы начинаете с преобразования будущего в Task, затем оборачиваете эту задачу в Coeval, а затем скрещиваете пальцы, ожидая, что вы получите. Это очень простой пример, чтобы показать, как это работает:

Вам нужен неявный Scheduler, чтобы иметь возможность его использовать:

import monix.execution.Scheduler.Implicits.global


Coeval(Task.fromFuture(Future (42)).runSyncStep).value() match {
   case Right(v) => println(v)
   case Left(task) => println("Task did not finish")
}

Вышеуказанное завершается и печатает 42 на консоли.

Coeval(Task.fromFuture(Future {
   scala.concurrent.blocking {
      42
   }
}).runSyncStep).value() match {
   case Right(v) => println(v)
   case Left(task) => println("Task did not finish")
}

В этом примере печатается Task did not finish: