假设我有4个未来的计算要做。前两个可以并行完成,但第三个必须在前两个之后完成(即使前三个的值未在第三个中使用 - 将每个计算视为执行某些db操作的命令)。最后,还有第4次计算必须在所有前3次计算之后发生。此外,还有一个副作用可以在前3次完成后开始(想想这是开始周期性的runnable)。在代码中,这可能如下所示:
for {
_ <- async1 // not done in parallel with async2 :( is there
_ <- async2 // any way of achieving this cleanly inside of for?
_ <- async3
_ = sideEffect // do I need "=" here??
_ <- async4
} yield ()
评论显示我对代码质量的怀疑:
答案 0 :(得分:2)
您可以使用zip
合并两个期货,包括zip
本身的结果。你最终会得到包含元组的元组,但如果你使用Tuple2
的中缀表示法,很容易将它们分开。下面我为简洁定义了一个同义词~
(这是解析器组合器库所做的,除了它的~
是一个与Tuple2
类似的不同类。)
作为副作用_ =
的替代方法,您可以将其移至yield
,或使用大括号和分号将其与以下语句合并。我仍然认为_ =
更加惯用,至少在for
中有一个副作用声明是完全惯用的。
val ~ = Tuple2
for {
a ~ b ~ c <- async1 zip
async2 zip
async3
d <- { sideEffect; async4 }
} yield (a, b, c, d)
答案 1 :(得分:2)
for-comprehensions表示monadic操作,monadic操作按顺序排列。有monad的超类,applicative,其中计算不依赖于先前计算的结果,因此可以并行运行。
Scalaz有一个|@|
运算符用于组合应用程序,因此您可以使用(future1 |@| future2)(proc(_, _))
并行调度两个期货,然后对它们的结果运行“proc”,而不是顺序计算for {a <- future1; b <- future2(a)} yield b
(或仅future1 flatMap future2
)。
stdlib Futures上已经有一种名为.zip的方法,它将Futures并行组合,实际上scalaz impl使用了这个方法:https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/std/Future.scala#L36 并且.zip和for-comprehensions可以混合以具有并行和顺序部分,视情况而定。 因此,只需使用stdlib语法,上面的示例可以写成:
for {
_ <- async1 zip async2
_ <- async3
_ = sideEffect
_ <- async4
} yield ()
或者,写出w / out for for-understanding:
async1 zip async2 flatMap (_=> async3) flatMap {_=> sideEffect; async4}
答案 2 :(得分:1)
您的代码已经看起来结构化了减去计算未来的并行。
带帮助程序的示例代码。
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Example {
def run: Future[Unit] = {
for {
(a, b, c) <- par(
Future.successful(1),
Future.successful(2),
Future.successful(3)
)
constant = 100
(d, e) <- par(
Future.successful(a + 10),
Future.successful(b + c)
)
} yield {
println(constant)
println(d)
println(e)
}
}
def par[A,B](a: Future[A], b: Future[B]): Future[(A, B)] = {
for {
a <- a
b <- b
} yield (a, b)
}
def par[A,B,C](a: Future[A], b: Future[B], c: Future[C]): Future[(A, B, C)] = {
for {
a <- a
b <- b
c <- c
} yield (a, b, c)
}
}
Example.run
编辑:
为1到20个期货生成代码:https://gist.github.com/nanop/c448db7ac1dfd6545967#file-parhelpers-scala
parPrinter脚本:https://gist.github.com/nanop/c448db7ac1dfd6545967#file-parprinter-scala
答案 3 :(得分:1)
就像一个FYI一样,让两个期货并行运行并且仍然通过理解来处理它们非常简单。使用zip
的建议解决方案当然可以工作,但我发现当我想要处理一些未来并在完成所有事情时做一些事情,并且我有两个或更多相互独立的时候,我做这样的事情:
val f1 = async1
val f2 = async2
//First two futures now running in parallel
for {
r1 <- f1
r2 <- f2
_ <- async3
_ = sideEffect
_ <- async4
} yield {
...
}
现在,在检查f1
的完成状态之前,理解结构的方式确实等待f2
,但这两个期货背后的逻辑正在同时运行。这比一些建议稍微简单一点,但仍然可以为您提供所需。