需要澄清斯卡拉的未来和承诺

时间:2013-09-23 13:21:06

标签: scala

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

我一直在阅读Scala文档中的Futures and Promises并且有点困惑,因为我觉得承诺和未来的概念是混杂的。

  

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

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

我迷失的地方是未来的承诺?

我已经在Clojure中读到了这些概念,假设承诺和未来有一些通用的共同概念,但似乎我错了。

  

承诺p完成了p.future返回的未来。这个未来是   具体到承诺p。取决于实施,它可能是   p.future eq p。

的情况
val p = promise[T]
val f = p.future

3 个答案:

答案 0 :(得分:69)

您可以将期货和承诺视为管道的两个不同方面。 在承诺方面,数据被推入,并且在未来方面,数据可以被拉出。

  

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

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

有一个名为future的未来构造函数返回这样一个占位符对象会产生一个完成此占位符对象的异步计算并不意味着异步计算被称为将来。还有其他future constructors/factory methods

  

但我没有得到的是承诺有何未来?

将承诺和期货划分为2个独立的界面是一个设计决策。您可以在同一个接口Future下使用这两个接口,但这样就可以让期货客户完成它们而不是未来的预期完成者。这会导致意外错误,因为可能存在任意数量的竞争完成者。

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

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

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

编辑:

如果您想了解有关未来的更多信息,请参阅Scala学习并发编程手册中的第4章详细介绍它们。免责声明:我是这本书的作者。

答案 1 :(得分:13)

两者之间的区别在于,期货通常以计算为中心,而承诺以数据为中心。

看来你的理解与此相符,但让我解释一下我的意思:

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

// scala
future { do_something() }

;; clojure
(future (do-something))

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

相反,在两种情况下, promises 都是一个最初为空的容器,以后可以填充(只需一次):

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

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

一旦出现这种情况,就可以阅读。

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

此时,重新考虑这两个概念的初步表征可能是值得的:

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

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

答案 2 :(得分:5)

请注意,幕后Future是根据Promise实施的,此Promise已完成,您传递给Future的正文:

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

impl.Future是Future trait的实现:

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

相关问题