【问题标题】:Scala - ScheduledFuture斯卡拉——预定未来
【发布时间】:2013-04-27 21:53:58
【问题描述】:

我正在尝试在 Scala 中实现预定的未来。我希望它等待特定的时间然后执行主体。到目前为止,我尝试了以下简单的方法

val d = 5.seconds.fromNow

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

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

但我在未来得到了 TimeoutExcpetion。这甚至是正确的方法还是我应该简单地使用 Java 中的 ScheduledExecutor?

【问题讨论】:

    标签: scala future


    【解决方案1】:

    Akka 有 akka.pattern:

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

    “返回一个 scala.concurrent.Future,它将在指定的持续时间后以提供的值的成功或失败来完成。”

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

    【讨论】:

    • 可以在没有演员系统的情况下使用吗?
    • 真的不用说为什么这对并发性很好,这是没用的
    • @matanster 你怎么看出来的?
    • 当然,但这不是问题的一部分。
    • 一个示例如何使用将被处理。这个怎么称呼?作为调度程序应该传递什么?
    【解决方案2】:

    单独使用标准库没有任何开箱即用的功能。 对于大多数简单的用例,您可以使用这样的小助手:

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

    可以这样使用:

    import scala.concurrent.duration._
    import scala.concurrent.ExecutionContext.Implicits._
    
    DelayedFuture( 5 seconds )( println("Hello") )
    

    请注意,与 java 计划的未来不同,此实现不会让您取消未来。

    【讨论】:

    • 我不明白一件事:有一个 implicit ctx: ExecutionContext 参数可以应用,但我看不到它会在哪里使用 - 我没有看到 makeTasktimer.schedule 期待它。
    • 我仔细看了一下,其实我肯定需要通过ExecutionContext。罪魁祸首在别处:我应该在 ExecutionContext 上运行主体,而不是在 Timer 的后台线程上运行它(这意味着在应用程序范围内,您实际上对于每个延迟的未来都有一个线程,这不好)。即使它本质上只是一个例子,我也很草率。新版本更适合生产。
    • def run(): Unit = prom.complete(Try(block())) 代替手动异常包装怎么样?
    • 一旦 Scala (2.12?) 支持 SAM,您的解决方案有望变得更清晰、更短。
    • 我注意到private val timer = new Timer 使用不作为守护线程运行的默认设置,因此可能会使用此代码停止关闭处理以安排偶尔的任务?也许最好使用private val timer = new Timer(true) 来防止进程挂起?
    【解决方案3】:

    如果您想在没有 Akka 的情况下安排完成,您可以使用常规 Java 计时器来安排完成承诺:

    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
    }
    

    【讨论】:

    • 你确定语法吗?我认为block不是一个函数,它不应该用作block(),而应该用作block
    • 另外,类似于我对stackoverflow.com/a/16363444/16673 的评论 - 你在某处使用executor 吗?
    • @Suma 这就是我在没有测试的情况下从内存中编写代码的方式。我已经修复了代码并确保它按照我声称的那样做
    • 每次都创建一个新的Timer 实例可能不是一个好主意
    【解决方案4】:

    我的解决方案与 Régis 的非常相似,但我使用 Akka 来安排:

     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
      }
    

    【讨论】:

      【解决方案5】:

      你可以把你的代码改成这样:

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

      但我不会推荐它。这样做,您将在 Future 中阻塞(阻塞以等待永远不会完成的 Promise),我认为在 ExecutionContext 中阻塞是非常不鼓励的。我会按照您所说的那样考虑使用 java 调度执行程序,或者您可以按照@alex23 的建议考虑使用 Akka。

      【讨论】:

      • Await.ready 使用blocking,所以如果你在这五秒钟内工作,至少底层池可以为它启动一个线程。
      • 很高兴看到这个(或原始问题)工作所需的导入。我认为它们是 scala.concurrent.duration._ 和 scala.concurrent._
      【解决方案6】:

      所有其他解决方案都使用 akka 或阻塞每个延迟任务的线程。一个更好的解决方案(除非你已经在使用 akka)是使用 java 的 ScheduledThreadPoolExecutor。这是一个 scala 包装器的示例:

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

      【讨论】:

      • 我的答案展示了如何在没有 akka 或阻止任何线程的情况下做到这一点,并且是一年多前发布的
      • 谢谢,我一定是错过了你的回答,我喜欢它,在我的解决方案中很少需要执行线程池。
      【解决方案7】:

      对此的最短解决方案,可能是使用 scala-async:

      import scala.async.Async.{async, await}
      
      def delay[T](value: T, t: duration): Future[T] = async {
        Thread.sleep(t.toMillis)
        value
      }
      

      或者如果你想延迟执行一个块

      def delay[T](t: duration)(block: => T): Future[T] async {
        Thread.sleep(t.toMillis)
        block()
      }
      

      【讨论】:

      • 这是一个非常不充分的解决方案。如前所述,这将阻塞一个线程。
      • 两件事:在这两种情况下你都没有错过嵌套的 await { ... } 吗?这将阻止线程阻塞并导致实际的延迟未来。或者,为什么不直接将 Thread.sleep(...) 调用封装在阻塞 { ... } 块中来构造 Future,以防止潜在的死锁?
      猜你喜欢
      • 2017-05-13
      • 1970-01-01
      • 2011-09-16
      • 2016-01-31
      • 2017-10-23
      • 1970-01-01
      • 2017-01-19
      • 1970-01-01
      • 2021-06-09
      相关资源
      最近更新 更多