【问题标题】:Return Future[(Int,Int)] instead of (Future[Int],Future[Int])返回 Future[(Int,Int)] 而不是 (Future[Int],Future[Int])
【发布时间】:2017-05-05 20:35:37
【问题描述】:

我有以下未来:

def f1 = Future {1}    
def f2 = Future {2}

我需要以下代码来返回Future[(Int,Int)]

val future = // function that returns a future
future.flatMap {
    val x1 = f1
    val x2 = f2
    (x1,x2)  // This returns Future[Int],Future[Int] 
}

我需要返回Future[(Int,Int)] 的函数,而不是(Future[Int],Future[Int])。如何转换它?

【问题讨论】:

标签: scala


【解决方案1】:

我将在此处对(当前)接受的答案提出异议。根据我的评论,这并不是将两个期货 zip 结合在一起的真正正确 方式。正确的方法就是这样:

f1 zip f2

另一个答案:

for (x <- f1; y <- f2) yield (x, y)

虽然这会起作用,但在 f2 是一个产生未来的表达式的情况下(就像在这个问题中一样),它并不平行。如果是这种情况,f2 将在第一个 future 完成 [1] 之前构建。虽然zip 已经按照flatMap 以相同的方式实现,但因为它的参数是严格的,第二个future 已经在运行(当然取决于执行上下文)。

它也更简洁!

[1] - 这可以通过观察y(由f1 计算的值)在f2 的构造范围内来看出

附录

这很容易证明:

scala> import scala.concurrent._; import ExecutionContext.Implicits.global
import scala.concurrent._
import ExecutionContext.Implicits.global

scala> def createAndStartFuture(i: Int): Future[Int] = Future {
     |   println(s"starting $i in ${Thread.currentThread.getName} at ${java.time.Instant.now()}")
     |   Thread.sleep(20000L)
     |   i
     | }
createAndStartFuture: (i: Int)scala.concurrent.Future[Int]

有了这个:

scala> for (x <- createAndStartFuture(1); y <- createAndStartFuture(2)) yield (x, y)
starting 1 in scala-execution-context-global-34 at 2017-05-05T10:29:47.635Z
res15: scala.concurrent.Future[(Int, Int)] = Future(<not completed>)

// Waits 20s
starting 2 in scala-execution-context-global-32 at 2017-05-05T10:30:07.636Z

但带拉链

scala> createAndStartFuture(1) zip createAndStartFuture(2)
starting 1 in scala-execution-context-global-34 at 2017-05-05T10:30:45.434Z
starting 2 in scala-execution-context-global-32 at 2017-05-05T10:30:45.434Z
res16: scala.concurrent.Future[(Int, Int)] = Future(<not completed>)

【讨论】:

    【解决方案2】:

    这是 for 理解的经典用例。

    for {
        x1 <- f1
        x2 <- f2
    } yield (x1, x2)
    

    这相当于一个平面图后跟一个地图

    f1.flatMap(x1 => f2.map(x2 => (x1,x2)))
    

    由于在 x1 准备好之前您不会到达 f1.flatMap 的内部,因此此代码将按顺序执行。

    如果您希望期货同时执行,您可以在 for 理解之前实例化期货。

    val future1 = f1
    val future2 = f2
    for {
        x1 <- future1
        x2 <- future2
    } yield (x1, x2)
    

    【讨论】:

    • 应该指出,这个解决方案是次优的,因为它不会在f2 完成之前启动f1 :-(
    • @oxbow_lakes 使用 for 理解很容易选择执行策略。您可以在 for 理解之前简单地实例化期货以实现并发。这个问题从未提到它希望期货同时执行。
    • @oxbow_lakes 在很多情况下,顺序处理是想要的结果。
    • 这是一个次优的解决方案;这是更多的代码,它重新发明了轮子,如果你想要顺序计算,你就不会使用期货
    • @oxbow_lakes 它在性能上是相同的,使用内置的语言结构,并且更灵活,因为它可以轻松扩展到任意数量的期货。很多时候我需要执行一个异步操作,然后根据其结果执行另一个异步操作。
    猜你喜欢
    • 2015-11-03
    • 1970-01-01
    • 2021-10-27
    • 1970-01-01
    • 2019-01-13
    • 2022-11-05
    • 2018-03-21
    • 2019-08-12
    • 1970-01-01
    相关资源
    最近更新 更多