【问题标题】:Create Future without starting it无需启动即可创建未来
【发布时间】:2019-08-03 05:47:22
【问题描述】:

这是我之前question的后续行动

假设我想用我的函数创建一个未来,但不想立即启动它(即我不想调用val f = Future { ... // my function}

现在我看到可以这样做:

val p = 承诺[单位] val f = p.future map { _ => // 我的函数在这里 }

这是用我的函数而不执行它来创建未来的唯一方法吗?

【问题讨论】:

  • 也许你能说一下你需要完成什么?
  • 您想要延迟计算的事实使我认为您依赖于一些外部状态更改(副作用)。如果是这种情况,请使用演员...

标签: scala concurrency future


【解决方案1】:

你可以这样做

val p = Promise[Unit]()
val f = p.future

//... some code run at a later time
p.success {
// your function
}

稍后编辑:

我认为您正在寻找的模式可以这样封装:

class LatentComputation[T](f: => T) {
  private val p = Promise[T]()

  def trigger() { p.success(f) }

  def future: Future[T] = p.future
}

object LatentComputation {
  def apply[T](f: => T) = new LatentComputation(f)
}

你会这样使用它:

val comp = LatentComputation {
// your code to be executed later
}

val f = comp.future

// somewhere else in the code
comp.trigger()

【讨论】:

  • 我猜success 可以立即启动我的功能。如果我想稍后通过显式调用success(或任何其他方法)来启动它怎么办?
  • 这个想法很好,但你不能将 LatentComputation 链接在一起作为“for”表达式的 monad,如果一个计算完成它会触发 for 表达式中的下一个计算会很好
  • 您可以通过LatentComputation 的未来来执行此操作。只需创建一个实例并提取未来并将其用于理解。否则,只需使用承诺来完成此操作
【解决方案2】:

你总是可以用闭包来推迟创建,你不会马上得到未来的对象,但你会得到一个稍后调用的句柄。

type DeferredComputation[T,R] = T => Future[R]

def deferredCall[T,R](futureBody: T => R): DeferredComputation[T,R] =
  t => future {futureBody(t)}

def deferredResult[R](futureBody: => R): DeferredComputation[Unit,R] =
  _ => future {futureBody}

【讨论】:

    【解决方案3】:

    如果您对执行控制过于花哨,也许您应该改用演员?

    或者,也许,您应该使用 Promise 而不是 FuturePromise 可以传递给其他人,而您保留它以便以后“实现”它。

    【讨论】:

    • +1。人们评论说,出于这个原因,他们对未来与演员持观望态度。但是,我发现当我变得太花哨时,那是因为我不知道自己在做什么。有时我什至不知道我为什么要这样做。但我一直在这样做。
    【解决方案4】:

    也值得给Promise.completeWith一个插头。

    您已经知道如何使用p.future onComplete mystuff

    您可以使用 p completeWith f 从另一个未来触发它。

    【讨论】:

      【解决方案5】:

      你也可以定义一个创建并返回Future的函数,然后调用它:

      val double = (value: Int) => {
        val f = Future { Thread.sleep(1000); value * 2 }
        f.onComplete(x => println(s"Future return: $x"))
        f
      }
      
      println("Before future.")
      double(2)
      println("After future is called, but as the future takes 1 sec to run, it will be printed before.")
      

      我用它来执行 n 批次的期货,比如:

      // The functions that returns the future.
      val double = (i: Int) => {
        val future = Future ({
          println(s"Start task $i")
          Thread.sleep(1000)
          i * 2
        })
      
        future.onComplete(_ => {
          println(s"Task $i ended")
        })
      
        future
      }
      
      val numbers = 1 to 20
      
      numbers
        .map(i => (i, double))
        .grouped(5)
        .foreach(batch => {
          val result = Await.result( Future.sequence(batch.map{ case (i, callback) => callback(i) }), 5.minutes )
          println(result)
        })
      

      【讨论】:

        【解决方案6】:

        或者只使用返回期货的常规方法,并使用类似 for 理解(顺序调用站点评估)之类的东西连续触发它们

        【讨论】:

          【解决方案7】:

          标准库 Future 的这个众所周知的问题:它们的设计方式使得它们在引用上不透明,因为它们急切地评估并记住他们的结果。在大多数用例中,这完全没问题,Scala 开发人员很少需要创建未评估的未来。

          采取以下程序:

          val x = Future(...); f(x, x) 
          

          不是同一个程序

          f(Future(...), Future(...))
          

          因为在第一种情况下,future 被评估一次,在第二种情况下,它被评估两次。

          这些库提供必要的抽象来处理引用透明的异步任务,除非开发人员明确要求,否则这些任务的评估会被延迟且不会被记忆。

          1. Scalaz 任务
          2. Monix 任务
          3. fs2

          如果您想使用 Cats,Cats 效果可以很好地与 Monix 和 fs2 配合使用。

          【讨论】:

            【解决方案8】:

            这有点小技巧,因为它与未来的工作方式无关,但只需添加惰性就足够了: lazy val f = Future { ... // my function} 但请注意,这也是一种类型更改,因为每当您引用它时,您都需要将引用声明为惰性引用,否则它将被执行。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-04-23
              • 1970-01-01
              • 1970-01-01
              • 2013-07-23
              相关资源
              最近更新 更多