如何在Scala中实现Future as应用程序?
假设我需要运行两个并发计算,等待它们,然后合并它们的结果。更具体地说,我需要同时运行如何在Scala中实现Future as应用程序?,scala,concurrency,functional-programming,future,applicative,Scala,Concurrency,Functional Programming,Future,Applicative,假设我需要运行两个并发计算,等待它们,然后合并它们的结果。更具体地说,我需要同时运行f1:X1=>Y1和f2:X2=>Y2,然后调用f:(Y1,Y2)=>Y,以最终获得Y的值 我可以创建未来的计算fut1:X1=>future[Y1]和fut2:X2=>future[Y2],然后使用一元合成将它们合成为fut:(X1,X2)=>future[Y] 问题是一元合成意味着顺序等待。在我们的例子中,这意味着我们先等待一个未来,然后再等待另一个未来。例如。如果需要2秒。到第一个未来完成,只需1秒。为了第
f1:X1=>Y1
和f2:X2=>Y2
,然后调用f:(Y1,Y2)=>Y
,以最终获得Y
的值
我可以创建未来的计算fut1:X1=>future[Y1]
和fut2:X2=>future[Y2]
,然后使用一元合成将它们合成为fut:(X1,X2)=>future[Y]
问题是一元合成意味着顺序等待。在我们的例子中,这意味着我们先等待一个未来,然后再等待另一个未来。例如。如果需要2秒。到第一个未来完成,只需1秒。为了第二次失败,我们浪费了1秒
所以,看起来我们需要一个实用的期货组合来等待,直到两者都完成或者至少有一个期货失败。这有意义吗?您将如何为期货实施
?它不需要是连续的。未来计算可能从未来被创建的那一刻开始。当然,如果未来是由flatMap参数创建的(如果它需要第一次计算的结果,则必须如此),那么它将是连续的。但在代码中,例如
val f1 = Future {....}
val f2 = Future {....}
for (a1 <- f1; a2 <- f2) yield f(a1, a2)
val f1=Future{….}
val f2=未来{….}
对于(a1你的帖子似乎包含两个或多或少独立的问题。
我将首先讨论运行两个并发计算的具体实际问题。最后回答了关于Applicative
的问题
假设您有两个异步函数:
val f1: X1 => Future[Y1]
val f2: X2 => Future[Y2]
和两个值:
val x1: X1
val x2: X2
现在你可以用多种不同的方式开始计算。让我们来看看其中的一些。
在之外为
(并行)启动计算
假设您这样做:
val y1: Future[Y1] = f1(x1)
val y2: Future[Y2] = f2(x2)
现在,计算f1
和f2
已经在运行。收集结果的顺序无关紧要。您可以使用for
-理解:
val y: Future[(Y1,Y2)] = for(res1 <- y1; res2 <- y2) yield (res1,res2)
转化为
val y = f1(x1).flatMap{ res1 => f2(x2).map{ res2 => (res1, res2) } }
特别是,第二次计算在第一次计算结束后开始。这通常不是人们想要的
在这里,违反了一个基本的替换原则。如果没有副作用,可以将此版本转换为前一版本,但在Scala中,必须明确地注意执行顺序
压缩期货(平行)
期货尊重产品。有一种方法Future.zip
,允许您执行以下操作:
val y = f1(x1) zip f2(x2)
这将并行运行两个计算,直到两个计算都完成,或者直到其中一个计算失败
演示
下面是一个演示这种行为的小脚本(灵感来自muhuk
的帖子):
实用的
使用以下定义:
我们可以这样做:
object FutureApplicative extends Applicative[Future] {
def apply[A, B](ff: Future[A => B]): Future[A] => Future[B] = {
fa => for ((f,a) <- ff zip fa) yield f(a)
}
}
objectfutureapplicative扩展了Applicative[Future]{
def应用[A,B](ff:Future[A=>B]):Future[A]=>Future[B]={
fa=>对于((f,a)
问题是,一元合成意味着顺序等待。在我们的例子中,它意味着我们先等待一个未来,然后再等待另一个未来
不幸的是,这是事实
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Test extends App {
def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
timestamp("Start")
for {
step1 <- Future {
Thread.sleep(2000)
timestamp("step1")
}
step2 <- Future {
Thread.sleep(1000)
timestamp("step2")
}
} yield { timestamp("Done") }
Thread.sleep(4000)
}
所以,看起来我们需要一个实用的期货组合来等待,直到两者都完成或者至少有一个期货失败
我不确定应用程序组合是否与并发策略有关。使用进行理解,如果所有未来都完成了,就会得到结果;如果其中任何一个失败了,就会得到失败。因此,语义上是相同的
为什么它们按顺序运行
我认为期货按顺序运行的原因是step1
在step2
中可用(以及在计算的其余部分)。基本上,我们可以将for
块转换为:
def step1() = Future {
Thread.sleep(2000)
timestamp("step1")
}
def step2() = Future {
Thread.sleep(1000)
timestamp("step2")
}
def finalStep() = timestamp("Done")
step1().flatMap(step1 => step2()).map(finalStep())
因此,前面计算的结果可用于其余步骤。在这方面,它与
&
不同
如何并行运行期货
@andrey tyukin的代码并行运行futures:
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Test extends App {
def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
timestamp("Start")
(Future {
Thread.sleep(2000)
timestamp("step1")
} zip Future {
Thread.sleep(1000)
timestamp("step2")
}).map(_ => timestamp("Done"))
Thread.sleep(4000)
}
输出:
Time: 2.028 seconds
Time: 3.001 seconds
Time: 2.001 seconds
Start: 1430474667418
step2: 1430474668444
step1: 1430474669444
Done: 1430474669446
其他答案中没有一种方法在未来很快失败的情况下做正确的事情,再加上未来在很长一段时间后成功
但这种方法可以手动实现:
def smartSequence[A](futures: Seq[Future[A]]): Future[Seq[A]] = {
val counter = new AtomicInteger(futures.size)
val result = Promise[Seq[A]]()
def attemptComplete(t: Try[A]): Unit = {
val remaining = counter.decrementAndGet
t match {
// If one future fails, fail the result immediately
case Failure(cause) => result tryFailure cause
// If all futures have succeeded, complete successful result
case Success(_) if remaining == 0 =>
result tryCompleteWith Future.sequence(futures)
case _ =>
}
}
futures.foreach(_ onComplete attemptComplete)
result.future
}
ScalaZ在内部做了类似的事情,因此f1 |@| f2
和List(f1,f2)。序列
在任何未来失败后立即失败
下面是这些方法失败时间的快速测试:
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scalaz._, Scalaz._
object ReflectionTest extends App {
def f1: Future[Unit] = Future {
Thread.sleep(2000)
}
def f2: Future[Unit] = Future {
Thread.sleep(1000)
throw new RuntimeException("Failure")
}
def test(name: String)(
f: (Future[Unit], Future[Unit]) => Future[Unit]
): Unit = {
val start = new Date().getTime
f(f1, f2).andThen {
case _ =>
println(s"Test $name completed in ${new Date().getTime - start}")
}
Thread.sleep(2200)
}
test("monadic") { (f1, f2) => for (v1 <- f1; v2 <- f2) yield () }
test("zip") { (f1, f2) => (f1 zip f2).map(_ => ()) }
test("Future.sequence") {
(f1, f2) => Future.sequence(Seq(f1, f2)).map(_ => ())
}
test("smartSequence") { (f1, f2) => smartSequence(Seq(f1, f2)).map(_ => ())}
test("scalaz |@|") { (f1, f2) => (f1 |@| f2) { case _ => ()}}
test("scalaz sequence") { (f1, f2) => List(f1, f2).sequence.map(_ => ())}
Thread.sleep(30000)
}
恐怕我不太明白。如果f1
在2秒内完成,而f2
在1秒内失败,我们需要等待多长时间。?很抱歉粗心地阅读了你的文章。我对你调用一元执行顺序有意见,但这确实会影响应用程序所能做的事情。谢谢,我同意关于s的说法实际执行是误导性的,这使得整个问题不够清楚。我将尝试重新表述。类似于Future.firstCompletedOf
的内容可能是您正在寻找的内容,但很难判断这两个选项中的哪一个完成了,如果成功了,还需要等待哪一个。如果您的结果是可识别的,那么它将继续uld更简单。有什么原因不能在标准库中使用第一个代码段的更简单版本而不是当前的实现?这正是我从zip
中所期望的。
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Test extends App {
def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
timestamp("Start")
(Future {
Thread.sleep(2000)
timestamp("step1")
} zip Future {
Thread.sleep(1000)
timestamp("step2")
}).map(_ => timestamp("Done"))
Thread.sleep(4000)
}
Start: 1430474667418
step2: 1430474668444
step1: 1430474669444
Done: 1430474669446
def smartSequence[A](futures: Seq[Future[A]]): Future[Seq[A]] = {
val counter = new AtomicInteger(futures.size)
val result = Promise[Seq[A]]()
def attemptComplete(t: Try[A]): Unit = {
val remaining = counter.decrementAndGet
t match {
// If one future fails, fail the result immediately
case Failure(cause) => result tryFailure cause
// If all futures have succeeded, complete successful result
case Success(_) if remaining == 0 =>
result tryCompleteWith Future.sequence(futures)
case _ =>
}
}
futures.foreach(_ onComplete attemptComplete)
result.future
}
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scalaz._, Scalaz._
object ReflectionTest extends App {
def f1: Future[Unit] = Future {
Thread.sleep(2000)
}
def f2: Future[Unit] = Future {
Thread.sleep(1000)
throw new RuntimeException("Failure")
}
def test(name: String)(
f: (Future[Unit], Future[Unit]) => Future[Unit]
): Unit = {
val start = new Date().getTime
f(f1, f2).andThen {
case _ =>
println(s"Test $name completed in ${new Date().getTime - start}")
}
Thread.sleep(2200)
}
test("monadic") { (f1, f2) => for (v1 <- f1; v2 <- f2) yield () }
test("zip") { (f1, f2) => (f1 zip f2).map(_ => ()) }
test("Future.sequence") {
(f1, f2) => Future.sequence(Seq(f1, f2)).map(_ => ())
}
test("smartSequence") { (f1, f2) => smartSequence(Seq(f1, f2)).map(_ => ())}
test("scalaz |@|") { (f1, f2) => (f1 |@| f2) { case _ => ()}}
test("scalaz sequence") { (f1, f2) => List(f1, f2).sequence.map(_ => ())}
Thread.sleep(30000)
}
Test monadic completed in 2281
Test zip completed in 2008
Test Future.sequence completed in 2007
Test smartSequence completed in 1005
Test scalaz |@| completed in 1003
Test scalaz sequence completed in 1005