52

我试图了解 Scala 的承诺和未来的构造。

我一直在阅读Scala 文档中的Futures and Promises并且有点困惑,因为我感觉 Promise 和 Futures 的概念混淆了。

在我的理解中,promise 是一个容器,我们可以在以后填充值。未来是某种异步操作,将在不同的执行路径中完成。

在 Scala 中,我们可以使用附加的回调来获得结果。

我迷失的地方是承诺如何拥有未来?

我也在 Clojure 中阅读过这些概念,假设 promise 和 future 有一些通用的通用概念,但似乎我错了。

一个承诺 p 完成了 p.future 返回的未来。这个未来是特定于承诺 p 的。根据实现,可能是 p.future eq p.

val p = promise[T]
val f = p.future
4

3 回答 3

79

您可以将期货和承诺视为管道的两个不同侧面。在promise端,数据被推入,在future端,数据可以被拉出。

未来是某种异步操作,将在不同的执行路径中完成。

实际上,future 是一个值的占位符对象,它可能在某个时间点异步可用。它不是异步计算本身。

有一个调用的 future 构造函数future返回这样一个占位符对象产生一个完成此占位符对象的异步计算这一事实并不意味着异步计算称为future。还有其他未来的构造函数/工厂方法

但我不明白的一点是承诺如何有未来?

将 Promise 和 Futures 划分为 2 个独立的接口是一个设计决策。您可以将这两个放在同一个界面下Future,但这将允许期货的客户完成它们,而不是未来的预期完成者。这将导致意外错误,因为可能有任意数量的竞争完成者。

例如,对于由future构造产生的异步计算,它是否必须完成承诺,或者客户端是否会这样做,将不再清楚。

期货和承诺旨在约束程序中的数据流。这个想法是让未来的客户端订阅数据以在数据到达后对其进行操作。Promise 客户端的作用是提供该数据。混合这两个角色可能会导致程序更难理解或推理。

您可能还会问为什么Promise特征不扩展Future。这是另一个设计决策,以阻止程序员盲目地将Promises 传递给他们应该向上转换的客户端PromiseFuture这种向上转换很容易被忽略,而必须显式调用future承诺确保您每次都调用它)。换句话说,通过返回一个承诺,您将有权完成它给其他人,通过返回未来,您将有权订阅它。

编辑:

如果您想了解更多关于期货的信息,《学习 Scala 中的并发编程》一书的第 4 章详细介绍了它们。免责声明:我是本书的作者。

于 2013-09-23T14:30:07.723 回答
18

两者的区别在于,future 通常以计算为中心,而 promise 以数据为中心。

您的理解似乎与此相符,但让我解释一下我的意思:

在 scala 和 clojure中, futures(除非由其他函数/方法返回)是通过一些计算创建的:

// scala
future { do_something() }

;; clojure
(future (do-something))

在这两种情况下,只有在计算终止后才能读取(没有阻塞)未来的“返回值”。这种情况通常不在程序员的控制范围内,因为计算是在后台的某个线程(池)中执行的。

相反,在这两种情况下,promise最初都是一个空容器,稍后可以填充(恰好一次):

// scala
val p = promise[Int]
...
p success 10 // or failure Exception()

;; clojure
(def p (promise))
(deliver p 10) 

一旦出现这种情况,它就可以被读取。

读取 futures 和 promises 是deref在 clojure 中完成的(并且realized?可以用来检查是否deref会阻塞)。在 Scala 中,阅读是通过Futuretrait 提供的方法完成的。为了读取 promise 的结果,我们必须获得一个实现 Future 的对象,这是由p.future. 现在如果 traitFuture由 a 实现Promise,则p.future可以返回this并且两者相等。这纯粹是一种实现选择,不会改变概念。所以你没有错! 在任何情况下,Futures 主要使用回调来处理。

在这一点上,重新考虑这两个概念的初始特征可能是值得的:

期货表示将在某个时刻产生结果的计算。让我们看一个可能的实现:我们在某个线程(池)中运行代码,一旦完成,我们安排使用返回值来履行承诺。所以阅读未来的结果就是阅读一个承诺;这是clojure的思维方式(不一定是实现)。

另一方面,promise 表示将在某个时候填充的值。当它被填满时,这意味着一些计算产生了一个结果。所以在某种程度上这就像一个未来的完成,所以我们应该以同样的方式使用这个值,使用回调;这是斯卡拉的思维方式。

于 2013-09-23T14:31:38.883 回答
7

请注意,引擎盖下的实现Future是根据您传递给您的主体完成的:PromisePromiseFuture

def apply[T](body: =>T): Future[T] = impl.Future(body)  //here I have omitted the implicit ExecutorContext

impl.Future 是Futuretrait 的实现:

def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T] =
{
  val runnable = new PromiseCompletingRunnable(body)
  executor.prepare.execute(runnable)
  runnable.promise.future
}

哪里PromiseCompletingRunnable看起来像这样:

class PromiseCompletingRunnable[T](body: => T) extends Runnable {
val promise = new Promise.DefaultPromise[T]()

override def run() = {
  promise complete {
    try Success(body) catch { case NonFatal(e) => Failure(e) }
  }
} }

因此,您会看到,即使它们是您可以独立使用的独立概念,但实际上您无法Future不使用Promise.

于 2014-05-21T06:15:58.383 回答