简体   繁体   English

Scala未来序列和超时处理

[英]Scala future sequence and timeout handling

There are some good hints how to combine futures with timeouts . 如何将期货与超时结合起来有一些很好的提示。 However I'm curious how to do this with Future sequence sequenceOfFutures 但是我很好奇如何用Future sequence sequenceOfFutures做到这一点

My first approach looks like this 我的第一种方法看起来像这样

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

object FutureSequenceScala extends App {
  println("Creating futureList")

  val timeout = 2 seconds
  val futures = List(1000, 1500, 1200, 800, 2000) map { ms =>
    val f = future {
      Thread sleep ms
      ms toString
    }
    Future firstCompletedOf Seq(f, fallback(timeout))
  }

  println("Creating waitinglist")
  val waitingList = Future sequence futures
  println("Created")

  val results = Await result (waitingList, timeout * futures.size)
  println(results)

  def fallback(timeout: Duration) = future {
    Thread sleep (timeout toMillis)
    "-1"
  }
}

Is there a better way to handle timeouts in a sequence of futures or is this a valid solution? 有没有更好的方法来处理一系列期货中的超时或这是一个有效的解决方案?

There are a few things in your code here that you might want to reconsider. 您的代码中有一些内容可能需要重新考虑。 For starters, I'm not a huge fan of submitting tasks into the ExecutionContext that have the sole purpose of simulating a timeout and also have Thread.sleep used in them. 对于初学者来说,我不是将任务提交到ExecutionContext中的忠实粉丝,其唯一目的是模拟超时并且还使用了Thread.sleep The sleep call is blocking and you probably want to avoid having a task in the execution context that is purely blocking for the sake of waiting a fixed amount of time. sleep调用是阻塞的,您可能希望避免在执行上下文中有一个任务,为了等待一段固定的时间而完全阻塞。 I'm going to steal from my answer here and suggest that for pure timeout handling, you should use something like I outlined in that answer. 我将从这里的答案中窃取并建议对于纯超时处理,你应该使用我在答案中概述的内容。 The HashedWheelTimer is a highly efficient timer implementation that is mush better suited to timeout handling than a task that just sleeps. HashedWheelTimer是一个高效的计时器实现,比只是睡眠的任务更适合超时处理。

Now, if you go that route, the next change I would suggest concerns handling the individual timeout related failures for each future. 现在,如果你走这条路线,下一次改变我会建议处理每个未来的个别超时相关故障。 If you want an individual failure to completely fail the aggregate Future returned from the sequence call, then do nothing extra. 如果您希望单个故障完全失败,则从sequence调用返回的聚合Future ,则不执行任何额外操作。 If you don't want that to happen, and instead want a timeout to return some default value instead, then you can use recover on the Future like this: 如果您不希望发生这种情况,而是希望超时返回一些默认值,那么您可以在Future上使用recover ,如下所示:

withTimeout(someFuture).recover{
  case ex:TimeoutException => someDefaultValue
}

Once you've done that, you can take advantage of the non-blocking callbacks and do something like this: 完成后,您可以利用非阻塞回调并执行以下操作:

waitingList onComplete{
  case Success(results) => //handle success
  case Failure(ex) => //handle fail
}

Each future has a timeout and thus will not just run infinitely. 每个未来都有一个超时,因此不会无限运行。 There is no need IMO to block there and provide an additional layer of timeout handling via the atMost param to Await.result . 没有必要IMO阻止那里并通过atMost参数向Await.result提供额外的超时处理层。 But I guess this assumes you are okay with the non-blocking approach. 但我想这可以假设你对非阻塞方法没问题。 If you really need to block there, then you should not be waiting timeout * futures.size amount of time. 如果你真的需要在那里阻止,那么你不应该等待timeout * futures.size的时间量。 These futures are running in parallel; 这些期货并行运行; the timeout there should only need to be as long as the individual timeouts for the futures themselves (or just slightly longer to account for any delays in cpu/timing). 那里的超时应该只需要与期货本身的个别超时一样长(或者只是稍微长一点来解释cpu /时间的任何延迟)。 It certainly should not be the timeout * the total number of futures. 它当然不应该是超时*期货的总数。

Here's a version that shows how bad your blocking fallback is. 这是一个显示阻止fallback有多糟糕的版本。

Notice that the executor is single threaded and you're creating many fallbacks. 请注意,执行程序是单线程的,您正在创建许多后备。

@cmbaxter is right, your master timeout shouldn't be timeout * futures.size , it should be bigger! @cmbaxter是对的,你的主超时不应该timeout * futures.size ,它应该更大!

@cmbaxter is also right that you want to think non-blocking. @cmbaxter也是对的,你想要非阻塞。 Once you do that, and you want to impose timeouts, then you will pick a timer component for that, see his linked answer (also linked from your linked answer). 一旦你这样做,并且你想要施加超时,那么你将选择一个计时器组件,查看他的链接答案(也链接到你的链接答案)。

That said, I still like my answer from your link , in so far as sitting in a loop waiting for the next thing that should timeout is really simple. 也就是说,我仍然喜欢你的链接中的答案 ,只要坐在循环中等待下一个应该超时的事情真的很简单。

It just takes a list of futures and their timeouts and a fallback value. 它只需要一份期货清单及其超时和后备价值。

Maybe there is a use case for that, such as a simple app that just blocks for some results (like your test) and must not exit before results are in. 也许有一个用例,例如一个简单的应用程序,只是阻止某些结果(如你的测试),并且必须在结果出来之前退出。

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

import java.util.concurrent.Executors
import java.lang.System.{ nanoTime => now }

object Test extends App { 
  //implicit val xc = ExecutionContext.global
  implicit val xc = ExecutionContext fromExecutorService (Executors.newSingleThreadExecutor)

  def timed[A](body: =>A): A = {
    val start = now 
    val res = body
    val end = now
    Console println (Duration fromNanos end-start).toMillis + " " + res
    res
  }
  println("Creating futureList")

  val timeout = 1500 millis
  val futures = List(1000, 1500, 1200, 800, 2000) map { ms =>
    val f = future {
      timed {
        blocking(Thread sleep ms)
        ms toString
      }
    } 
    Future firstCompletedOf Seq(f, fallback(timeout))
  }   

  println("Creating waitinglist")
  val waitingList = Future sequence futures
  println("Created")

  timed {
  val results = Await result (waitingList, 2 * timeout * futures.size)
  println(results)
  }     
  xc.shutdown

  def fallback(timeout: Duration) = future {
    timed {
      blocking(Thread sleep (timeout toMillis))
      "-1"
    }
  }   
}   

What happened: 发生了什么:

Creating futureList
Creating waitinglist
Created
1001 1000
1500 -1
1500 1500
1500 -1
1200 1200
1500 -1
800 800
1500 -1
2000 2000
1500 -1
List(1000, 1500, 1200, 800, 2000)
14007 ()

Monix Task has timeout support: Monix Task有超时支持:

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

  println("Creating futureList")
  val tasks = List(1000, 1500, 1200, 800, 2000).map{ ms =>
    Task {
      Thread.sleep(ms)
      ms.toString
    }.timeoutTo(2.seconds, Task.now("-1"))
  }

  println("Creating waitinglist")
  val waitingList = Task.gather(tasks) // Task.sequence is true/literally "sequencing" operation

  println("Created")
  val results = Await.result(waitingList, timeout * futures.size)
  println(results)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM