[英]Clarification needed about futures and promises in Scala
我正在努力了解 Scala 的承诺和未来的构造。
我一直在阅读 Scala 文档中的Futures and Promises并且有点困惑,因为我觉得 promises 和 futures 的概念混淆了。
在我的理解中,promise 是一个容器,我们可以在以后填充值。 而 future 是某种异步操作,它将在不同的执行路径中完成。
在 Scala 中,我们可以使用附加的回调函数获取结果。
我迷失的地方是承诺如何有未来?
我也在 Clojure 中阅读了这些概念,假设 promise 和 future 有一些通用的通用概念,但似乎我错了。
承诺 p 完成 p.future 返回的未来。 这个未来是特定于承诺 p 的。 根据实现,可能会出现 p.future eq p.
val p = promise[T]
val f = p.future
您可以将期货和承诺视为管道的两个不同侧面。 在promise端,数据被推入,在future端,数据可以被拉出。
而 future 是某种异步操作,它将在不同的执行路径中完成。
实际上,future 是一个值的占位符对象,该值可能在某个时间点异步可用。 它不是异步计算本身。
有一个称为future
的 future 构造函数返回这样一个占位符对象并产生一个异步计算来完成这个占位符对象的事实并不意味着异步计算被称为future 。 还有其他未来的构造函数/工厂方法。
但我不明白的一点是承诺如何有未来?
将 promises 和 futures 分成 2 个独立的接口是一个设计决策。 您可以在同一接口Future
下拥有这两个,但这将允许期货的客户完成它们,而不是未来的预期完成者。 这会导致意外错误,因为可能有任意数量的竞争完成者。
例如,对于由future
构造产生的异步计算,它是否必须完成承诺,或者客户端是否会这样做将不再清楚。
期货和承诺旨在限制程序中的数据流。 这个想法是让未来的客户端订阅数据,以便在数据到达后对其进行操作。 承诺客户端的作用是提供该数据。 混合这两个角色会导致程序更难理解或推理。
您可能还会问为什么Promise
trait 不扩展Future
。 这是另一个设计决策,旨在阻止程序员盲目地将Promise
传递给客户端,他们应该将Promise
向上转换为Future
(这种向上转换很容易被排除在外,而必须在Promise
上显式调用future
可确保您每次都调用它)。 换句话说,通过返回承诺,您将完成它的权利授予其他人,通过返回未来,您将获得订阅它的权利。
编辑:
如果您想了解更多有关 future 的信息,请参阅 Learning Concurrent Programming in Scala 一书中的第 4 章详细介绍了它们。 免责声明:我是这本书的作者。
两者之间的区别在于期货通常以计算为中心,而承诺以数据为中心。
看来您的理解与此相符,但让我解释一下我的意思:
在 scala 和 clojure 中,期货(除非由某些其他函数/方法返回)是通过一些计算创建的:
// 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)
一旦出现这种情况,就可以读取了。
读取期货和承诺是通过 clojure 中的deref
完成的(并realized?
可用于检查deref
是否会阻塞)。 在 Scala 中,读取是通过Future
特性提供的方法完成的。 为了读取 promise 的结果,我们必须获得一个实现 Future 的对象,这是由p.future
完成的。 现在,如果 trait Future
是由Promise
实现的,那么p.future
可以返回this
并且两者是相等的。 这纯粹是一种实现选择,不会改变概念。 所以你没有看错! 在任何情况下,Future 主要是使用回调来处理的。
在这一点上,重新考虑这两个概念的初始特征可能是值得的:
Futures 表示将在某个时刻产生结果的计算。 让我们看看一种可能的实现:我们在某个线程(池)中运行代码,一旦完成,我们安排使用返回值来实现承诺。 所以阅读未来的结果就是阅读一个承诺; 这是 clojure 的思维方式(不一定是实现方式)。
另一方面,promise 表示将在某个时候填充的值。 当它被填满时,这意味着某些计算产生了结果。 所以在某种程度上这就像一个未来完成,所以我们应该以同样的方式使用这个值,使用回调; 这是scala的思维方式。
请注意,在底层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) }
}
} }
因此,您会看到,即使它们是您可以在现实中独立使用的单独概念,如果不使用Promise
就无法获得Future
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.