如何在scala中连续执行Futures

时间:2013-12-06 01:53:08

标签: scala

我有这种情况需要使用迭代器,对于调用函数f(item)的每个项目并返回Future[Unit]

但是,我需要让每个f(item)调用按顺序执行,它们不能并行运行。

for(item <- it)
  f(item)

不起作用,因为这会并行启动所有调用。

我该怎么做,以便按顺序进行?

7 个答案:

答案 0 :(得分:40)

如果您不介意非常本地化的var,您可以按如下方式序列化异步处理(每个f(item))(flatMap进行序列化):

val fSerialized = {
  var fAccum = Future{()}
  for(item <- it) {
    println(s"Processing ${item}")
    fAccum = fAccum flatMap { _ => f(item) }
  }
  fAccum
}

fSerialized.onComplete{case resTry => println("All Done.")}

一般情况下,避免Await操作 - 它们会阻止(有点像异步,消耗资源和设计草率,会死锁)


酷技巧1:

您可以通过通常的嫌疑人Futuresflatmap链接在一起 - 它会序列化异步操作。它有什么不能做的吗? ; - )

def f1 = Future { // some background running logic here...}
def f2 = Future { // other background running logic here...}

val fSerialized: Future[Unit] = f1 flatMap(res1 => f2)  

fSerialized.onComplete{case resTry => println("Both Done: Success=" + resTry.isSuccess)}

以上都没有 - 主线程在几十纳秒内直接运行。在所有情况下都使用Futures来执行并行线程并跟踪异步状态/结果和​​链逻辑。

fSerialized表示链接在一起的两个不同异步操作的组合。一旦评估了val,它就会立即启动f1(异步运行)。 f1像任何Future一样运行 - 当它最终完成时,它会调用它的onComplete回调块。这是很酷的位 - flatMap将它的参数安装为f1 onComplete回调块 - 所以f2f1完成后立即启动,没有阻塞,轮询或浪费资源用法。完成f2后,fSerialized即完成 - 因此它会运行fSerialized.onComplete回调块 - 打印“两个完成”。

不仅如此,您还可以使用整洁的非意大利面条代码尽可能多地链接平面地图

 f1 flatmap(res1 => f2) flatMap(res2 => f3) flatMap(res3 => f4) ...

如果你是通过Future.onComplete来做的,你必须将连续的操作嵌入到嵌套的onComplete图层中:

f1.onComplete{case res1Try => 
  f2
  f2.onComplete{case res2Try =>
    f3
    f3.onComplete{case res3Try =>
      f4
      f4.onComplete{ ...
      }
    }
  }
}

不太好。

测试证明:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.blocking
import scala.concurrent.duration._

def f(item: Int): Future[Unit] = Future{
  print("Waiting " + item + " seconds ...")
  Console.flush
  blocking{Thread.sleep((item seconds).toMillis)}
  println("Done")
}

val fSerial = f(4) flatMap(res1 => f(16)) flatMap(res2 => f(2)) flatMap(res3 => f(8))

fSerial.onComplete{case resTry => println("!!!! That's a wrap !!!! Success=" + resTry.isSuccess)}

酷技巧2:

这样的理解:

for {a <- aExpr; b <- bExpr; c <- cExpr; d <- dExpr} yield eExpr

只不过是语法糖:

aExpr.flatMap{a => bExpr.flatMap{b => cExpr.flatMap{c => dExpr.map{d => eExpr} } } }

这是一个flatMaps链,后面是最终地图。

这意味着

f1 flatmap(res1 => f2) flatMap(res2 => f3) flatMap(res3 => f4) map(res4 => "Did It!")

相同
for {res1 <- f1; res2 <- f2; res3 <- f3; res4 <- f4} yield "Did It!"

测试证明(继之前的测试之后):

val fSerial = for {res1 <- f(4); res2 <- f(16); res3 <- f(2); res4 <- f(8)} yield "Did It!"
fSerial.onComplete{case resTry => println("!!!! That's a wrap !!!! Success=" + resTry.isSuccess)}

不那么酷的技巧3:

不幸的是你无法混合迭代器和放大器。期货在同样的理解中。编译错误:

val fSerial = {for {nextItem <- itemIterable; nextRes <- f(nextItem)} yield "Did It"}.last

嵌套fors会带来挑战。以下不是序列化,而是并行运行异步块(嵌套的理解不会将后续的Futures与flatMap / Map链接,而是链为Iterable.flatMap {item =&gt; f(item)} - 不一样!)

val fSerial = {for {nextItem <- itemIterable} yield
                 for {nextRes <- f(nextItem)} yield "Did It"}.last

同样使用foldLeft / foldRight和flatMap并不像你期望的那样工作 - 似乎是一个错误/限制;所有异步块都是并行处理的(因此Iterator.foldLeft/RightFuture.flatMap不合作):

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.blocking
import scala.concurrent.duration._

def f(item: Int): Future[Unit] = Future{
  print("Waiting " + item + " seconds ...")
  Console.flush
  blocking{Thread.sleep((item seconds).toMillis)}
  println("Done")
}

val itemIterable: Iterable[Int] = List[Int](4, 16, 2, 8)
val empty = Future[Unit]{()}
def serialize(f1: Future[Unit], f2: Future[Unit]) = f1 flatMap(res1 => f2)

//val fSerialized = itemIterable.iterator.foldLeft(empty){(fAccum, item) => serialize(fAccum, f(item))}
val fSerialized = itemIterable.iterator.foldRight(empty){(item, fAccum) => serialize(fAccum, f(item))}

fSerialized.onComplete{case resTry => println("!!!! That's a wrap !!!! Success=" + resTry.isSuccess)}

但这有效(涉及var):

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.blocking
import scala.concurrent.duration._

def f(item: Int): Future[Unit] = Future{
  print("Waiting " + item + " seconds ...")
  Console.flush
  blocking{Thread.sleep((item seconds).toMillis)}
  println("Done")
}

val itemIterable: Iterable[Int] = List[Int](4, 16, 2, 8)

var fSerial = Future{()}
for {nextItem <- itemIterable} fSerial = fSerial.flatMap(accumRes => f(nextItem)) 

答案 1 :(得分:34)

def seqFutures[T, U](items: TraversableOnce[T])(yourfunction: T => Future[U]): Future[List[U]] = {
  items.foldLeft(Future.successful[List[U]](Nil)) {
    (f, item) => f.flatMap {
      x => yourfunction(item).map(_ :: x)
    }
  } map (_.reverse)
}

如果您按顺序运行,因为资源限制阻止一次运行多个Future,则创建和使用仅包含单个线程的自定义ExecutionContext可能更容易。

答案 2 :(得分:5)

另一个选择是使用Akka Streams:

val doneFuture = Source
  .fromIterator(() => it)
  .mapAsync(parallelism = 1)(f)
  .runForeach{identity}

答案 3 :(得分:0)

此代码向您展示如何使用简单的承诺按顺序运行期货来完成它。

代码包含两个顺序器,一个逐个执行工作,另一个允许您指定同时运行的数量。

例外情况无法保持简单。

import scala.concurrent.{Await, Future, Promise}
import scala.util.Try
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration

/**
  * Simple class to encapsulate work, the important element here is the future
  * you can ignore the rest
  */
case class Work(id:String, workTime:Long = 100) {
  def doWork(): Future[String] = Future {
    println(s"Starting $id")
    Thread.sleep(workTime)
    println(s"End $id")
    s"$id ready"
  }
}

/**
  * SimpleSequencer is the one by one execution, the promise is the element
  * who allow to the sequencer to work, pay attention to it.
  *
  * Exceptions are ignore, this is not production code
  */
object SimpleSequencer {
  private def sequence(works:Seq[Work], results:Seq[String], p:Promise[Seq[String]]) : Unit = {
    works match {
      case Nil => p.tryComplete(Try(results))
      case work::tail => work.doWork() map {
        result => sequence(tail, results :+ result, p)
      }
    }
  }

  def sequence(works:Seq[Work]) : Future[Seq[String]] = {
    val p = Promise[Seq[String]]()
    sequence(works, Seq.empty, p)
    p.future
  }
}

/**
  * MultiSequencer fire N works at the same time
  */
object MultiSequencer {
  private def sequence(parallel:Int, works:Seq[Work], results:Seq[String], p:Promise[Seq[String]]) : Unit = {
    works match {
      case Nil => p.tryComplete(Try(results))
      case work =>
        val parallelWorks: Seq[Future[String]] = works.take(parallel).map(_.doWork())
        Future.sequence(parallelWorks) map {
          result => sequence(parallel, works.drop(parallel), results ++ result, p)
        }
    }
  }

  def sequence(parallel:Int, works:Seq[Work]) : Future[Seq[String]] = {
    val p = Promise[Seq[String]]()
    sequence(parallel, works, Seq.empty, p)
    p.future
  }

}


object Sequencer {

  def main(args: Array[String]): Unit = {
    val works = Seq.range(1, 10).map(id => Work(s"w$id"))
    val p = Promise[Unit]()

    val f = MultiSequencer.sequence(4, works) map {
      resultFromMulti =>
        println(s"MultiSequencer Results: $resultFromMulti")
        SimpleSequencer.sequence(works) map {
          resultsFromSimple =>
            println(s"MultiSequencer Results: $resultsFromSimple")
            p.complete(Try[Unit]())
        }
    }

    Await.ready(p.future, Duration.Inf)
  }
}

答案 4 :(得分:0)

也许更优雅的解决方案是使用递归,如下面详述。

这可以作为返回Future的长操作的示例:

def longOperation(strToReturn: String): Future[String] = Future {
  Thread.sleep(5000)
  strToReturn
}

以下是遍历要处理的项目的递归函数,并按顺序处理它们:

def processItems(strToReturn: Seq[String]): Unit = strToReturn match {
  case x :: xs => longOperation(x).onComplete {
    case Success(str) =>
      println("Got: " + str)
      processItems(xs)
    case Failure(_) =>
      println("Something went wrong")
      processItems(xs)
  }
  case Nil => println("Done")
}

这是通过让函数递归调用自身,并在Future完成或失败后处理其余项目来完成的。

要开始此活动,请调用'processItems'函数,其中包含一些要处理的项目,如下所示:

processItems(Seq("item1", "item2", "item3"))

答案 5 :(得分:0)

仅仅扩展@ wingedsubmariner的答案,因为最后.reverse让我烦恼(并为完整性添加了导入语句)

import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}

def seqFutures[T, U](xs: TraversableOnce[T])(f: T => Future[U])
                    (implicit ec: ExecutionContext): Future[List[U]] = {
  val resBase = Future.successful(mutable.ListBuffer.empty[U])
  xs
    .foldLeft(resBase) { (futureRes, x) =>
      futureRes.flatMap {
        res => f(x).map(res += _)
      }
    }
    .map(_.toList)
}

注意: ListBuffer有固定时间+=.toList操作

答案 6 :(得分:-6)

您可以使用Await.result :(未经测试的代码)

“等待:用于阻止未来的单例对象(将其结果传输到当前线程)。”

val result  = item map {it => Await.result(f(it), Duration.Inf) }