Future与promise
异步处理 来自维基百科,自由的百科全书
在计算机科学中,future、promise、delay和deferred,是在某些并发编程语言中,指称用于同步程序执行的一种构造。由于某些计算尚未结束,故而需要一个对象来代理这个未知的结果。这种构造起源于函数式编程和相关范型如逻辑编程,其目的是将值与其运算过程解耦,从而允许更灵活地进行计算,特别是通过将其并行化来进行。后来它在分布式计算中得到了应用,用来减少网络通信往返的延迟。再后来async/await语法机制使其变得更有用,籍此允许以直接风格编写异步程序,而不再采用续体传递风格。
术语
在1976年Daniel P. Friedman和David Wise提出了术语“promise”[1],同年Peter Hibbard称之为“eventual”[2]。1977年Henry Baker和Carl Hewitt在一篇论文中介绍了一个类似的概念“future”[3]。
术语“future”、“promise”、“delay”和“deferred”通常用来称谓同样一种机制,在特定实现中可能只选用其中之一。在这种情况下,值与其运算过程是一起创建并且相互关联的:future若指称推迟设置的一个值[4],设置它的函数就可称谓为promise;promise若指称推迟设置的一个值[5],设置它的函数就可称谓为resolver;promise若指称一个异步函数[1],它的结果值所设置的就可称谓为future。设置一个推迟值也称为“决定/解决”(resolve)、“履行/实现”(fulfil)或“绑定”(bind)它。
推迟值及其设置者也存在区分称谓而同时使用的情况,二者的这种用法在不同实现中的差异将在专门章节中讨论。具体来说,future指称一个“只读”的变量的占位符视图,而promise指称一个可写的单赋值容器,使用其“成功”方法来设置这个future的值[6]。尤其是在定义future之时,无须指定设置其值的promise;并且可能有不同的promise,设置同一个future的值;但是对于一个给定future,仅可以进行一次设置。
历史
future及/或promise构造,首先实现于编程语言例如MultiLisp[4]和Act 1之中。在并发逻辑编程语言中,使用非常类似于future的逻辑变量进行通信[7]。这种语言开始于1982年的“Prolog with Freeze”和“IC Prolog”,并且在后来的语言中变成了真正的并发原语,比如Concurrent Prolog、Parlog、守卫霍恩子句(GHC)、Strand、Janus、Oz(Mozart)和Alice ML[8][9]。叫做“I-变量”的单赋值变量,非常类似于并发逻辑变量,它起源于数据流程编程语言Id的“I-结构”元件[10],并且包含在Reppy的Concurrent ML中[11]。
在1988年Barbara Liskov和Liuba Shrira,发明了promise流水线技术来克服传输延迟[5];Liskov和Shrira在论文中使用了术语“promise”,但是他们采用了现在少见的名称“call-stream”来提及流水线机制。在大约1989年Mark S. Miller、Dean Tribble和Rob Jellinghaus,于Xanadu项目中也独立发明了此技术[12]。
Liskov和Shrira的论文中描述的设计,以及Xanadu中的promise流水线的实现,都有一个限制,即promise值不是头等的:call或send的参数或返回值,不能直接是promise。在Liskov和Shrira论文中使用的编程语言Argus,直至大约1988年停止开发,似乎都未曾在任何公开发布中实现promise和call-stream[13][14]。Xanadu实现的promise流水线,仅在1999年Udanax Gold的源代码发布时才公开发布[15],并且在任何已发布的文档中都没有解释过[16]。
一些早期的演员语言,包括Act系列[17][18],支持并行消息传递和流水线式消息处理,但不支持promise流水线。Joule和E语言的后续实现,支持完全头等的promise和resolver,还有promise流水线。
2000年之后,由于消息模式的请求-响应模型,在用户界面响应力和Web开发中的应用,future和promise重新引起了人们的兴趣。现在一些主流语言对future和promise有了语言支持,最著名的是2004年发行的Java 5中的FutureTask
[19],以及2012年发行的.NET框架 4.5中的async
/await
结构[20][21],它在很大程度上受到可追溯到2007年的“F#异步编程模型”的启发[22][23]。async/await随后被其他语言采用,特别是2014年的Dart 1.9[24]、2014年发行的Hack(HHVM)、2015年的Python 3.5[25]、ECMAScript 2017、2019年的Rust 1.39和C++20等。
实现列表
下面列出支持future、promise和并发逻辑变量、数据流程变量或I-变量的语言,包括直接在语言中支持还有在标准库中支持:
- ABCL/f[26]
- Alice ML,支持future[9][27]
- AmbientTalk,包含头等的resolver和只读promise
- C++,从C++11开始有std::future和std::promise
- Concurrent ML,仅限I-变量和M-变量[11]
- Crystal
- Dart,使用Future/Completer类[28]以及关键字
async
/await
[24] - Elm,通过Task模块[29]
- Glasgow Haskell,仅限同步可变变量“MVars”[30]
- Id,仅限I-结构和M-结构[10]
- Io[31]
- Java,通过Future[32]或CompletableFuture[33]
- JavaScript,始于ECMAScript 2015提供
Promise
对象[34],并且自从ECMAScript 2017可通过关键字async
/await
来使用[35] - Lucid,仅限作为串流的变量
- 一些Lisp
- .NET,自.NET框架 4.0起通过TPL(任务并行库)[38]
- C#,自.NET框架 4.5起[20],通过关键字
async
/await
[21] - Visual Basic,自从版本11,通过关键字
Async
/Await
[21]
- C#,自.NET框架 4.5起[20],通过关键字
- Kotlin,但是
kotlin.native.concurrent.Future
通常只在书写本机运行代码时使用[39] - Nim,使用
async
/await
语法 - Oxygene
- Oz版本3 [40]
- Python,自从2011年版本3.2提供concurrent.futures模块[41][42],这是由PEP 3148所提议[43],而2015年版本3.5增加了基于低层future的
asyncio
模块,它采用高层async
/await
语法[44] - R,延迟计算的promise,仍然是单线程
- Raku[45]
- Rust,通常经由
.await
完成[46] - Scala,通过scala.concurrent包[47]
- Scheme,仅延迟求值
- Squeak Smalltalk
- Strand
还支持promise流水线的语言包括:
- E
- Joule
- 对于Common Lisp:
- 对于C++:
- 对于C#和其他.NET语言:
- Parallel Extensions库
- 对于Groovy:
- GPars[58]
- 对于JavaScript:
- Promises/A+标准网站[59],有此标准的实现的列表[60]
- Cujo.js[61]的when.js[62],提供的promise符合Promises/A+ 1.1标准
- Dojo Toolkit,提供promise和Twisted风格Deferred[63]
- MochiKit[64],受Twisted的Deferred的启发
- jQuery[65]的Deferred对象[66],基于了CommonJS Promises/A规定[67]
- AngularJS[68]
- Q[69],符合Promises/A+ 1.1标准
- RSVP.js[70],符合Promises/A+ 1.1标准
- Bluebird[71]
- Closure库的promise包[72],符合Promises/A+标准
- 对于Java:
- 对于Objective-C:
- 对于OCaml:
- Lazy模块实现了懒惰的显式future[82]
- 对于Perl:
- 对于PHP:
- React/Promise [86]
- 对于Python:
- 对于R:
- 对于Ruby:
- 对于Rust:
- futures-rs[94]
- 对于Scala:
- Twitter的util库[95]
- 对于Swift:
- 对于Tcl:
- tcl-promise[102]
future可以用协程或生成器实现[25],从而具有相同的求值策略(例如协同多任务或延迟求值)。
future还可以很容易地用通道实现:future是一个单元素的通道,而promise是一个发送到通道,实现future的过程。这允许future在支持通道(如CSP和Go)的并发编程语言中实现[103]。由此产生的future是显式的,因为它们必须通过从通道读取而不是仅仅通过求值来获取。
编程语言典型示例
Python的concurrent.futures
模块,提供了异步执行可调用对象的高层接口。异步执行可以使用线程池执行器ThreadPoolExecutor
,通过多个线程来进行;或使用进程池执行器ProcessPoolExecutor
,通过分立的多个进程来进行。concurrent.futures.Future
类封装了可调用对象的异步执行,其实例由执行器抽象类Executor
的这两个子类的submit()
方法创建[41]。
在下面的例子中,定义了load_url()
,用来检索一个URL所指定的单一网页并报告其内容。使用with
语句指定采用线程池执行器,这确保了线程由它及时清理。用这个执行器的submit()
方法启动所有的装载操作任务,并使用字典推导式为其创建的每个future标记上对应的URL。采用模块函数as_completed()
,在指定的Future
类的诸实例“已齐全”(completed)之时,建立在其上的迭代器。
import concurrent.futures
import urllib.request
URLS = [
'https://www.python.org/',
'https://pypi.org/search/',
'https://no-such-url'
]
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
with concurrent.futures.ThreadPoolExecutor() as executor:
future_to_url = {executor.submit(load_url, url, 60) : url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print(f'{url!r} generated an exception: {exc!s}')
else:
print(f'{url!r} page is {len(data):d} bytes')
在CPython中,由于全局解释器锁(GIL),保证一时只有一个线程可以执行Python字节码;一些扩展模块被设计为在进行计算密集任务时释放GIL,还有在进行I/O时总是释放GIL。线程池执行器的max_workers
参数的缺省值是min(32, os.cpu_count() + 4)
,这个缺省值为I/O踊跃任务保留至少5个worker线程,为释放GIL的CPU踊跃任务利用最多32个CPU逻辑核心。想要更好利用多核心机器的计算资源,可以使用进程池执行器或多进程模块multiprocessing
。对于同时运行多个I/O踊跃任务,仍然适合采用线程池执行器或线程模块threading
。
Python的异步I/O模块asyncio
,是采用async/await语法编写并发代码的库[104]。asyncio
模块基于低层事件循环,提供的高层API包括了:并发运行Python协程并拥有在其执行上的完全控制,进行网络IO和IPC,控制子进程,通过队列分布任务和同步并发代码。asyncio
模块的同步原语,在设计上类似于threading
模块的同步原语,但与之相比有两个重要差异:asyncio
模块的同步原语不是线程安全的,故而不能用于OS线程同步;这些同步原语的方法不接受超时实际参数。
asyncio
模块提供的低层API中有asyncio.Future
对象,用来桥接低层基于回调的代码和高层采用async
/await
语法的代码,它在设计上模仿了concurrent.futures.Future
对象。这两个模块的Future
对象的set_result()
方法标记这个Future
对象为“已完毕”(done)并设置它的结果,而set_exception()
方法标记这个Future
对象为已完毕并设置一个例外;done()
方法在这个Future
对象已完毕时返回True
;result()
方法返回这个Future
对象所设置的结果,或者引发其所设置的例外。
二者的关键差异包括了:asyncio.Future
是可期待(awaitable)对象,而concurrent.futures.Future
对象不可以被期待(awaited)。asyncio.Future.result()
不接受超时实际参数,如果此刻这个Future
对象的结果仍不可获得,它引发一个InvalidStateError
例外;而concurrent.futures.Future.result()
,可接受超时(timeout
)实际参数,如果此刻结果仍未“完全”(completed),它等待指定时间后若仍未完全则引发TimeoutError
例外,如果超时未指定或指定为None
,则对等待时间没有限制。
在JavaScript中,Promise
对象表示一个异步运算的最终完成或失败,及其结果值或失败理由。Promise
对象是对一个值的代理(proxy),这个值在创建这个promise之时不必需已知。promise允许为异步行动的最终成功值或失败理由关联上处理器,这使得异步方法像同步方法那样返回值:并非立即返回最终值,异步方法返回一个promise来在将来的某一点上提供这个值。
在JavaScript生态系统中,Promise
对象在成为语言本身的一部份之前很久就有了多种实现[105]。尽管各有不同的内部表示,至少几乎所有类似Promise
的对象都实现了“可接续”(thenable)接口[59]。可接续对象实现了.then()
方法,Promise
对象就是可接续的。
一个promise被称为已落实(settled),如果它要么已履行(fulfilled)要么已拒绝(rejected),而不再待定(pending)。Promise
实例的.then()
方法,接受一到二个实际参数,它们是作为处理器异步执行的回调函数,分别针对了这个promise的已履行和已拒绝情况;此方法返回一个新的promise,它决定(resolve)出其所调用处理器的返回值,或者在原来这个promise未被处理的情况下,仍决定出其已落实的值。Promise
实例的.catch()
方法,实际上就是置空针对已履行情况的回调函数,而只有针对已拒绝情况的回调函数的.then()
方法。
Promise()
构造子创建Promise
对象,它主要用于包装仍基于回调的API所提供的异步运算,要注意它只能通过new
算子来构造:new Promise(executor)
。Promise()
构造子的实际参数叫做“执行器”(executor),它是由这个构造子同步执行的一个函数,它有可自行指定名字的两个函数形式参数,比如指定为resolveFunc
和rejectFunc
,这两个函数接受任何类型的单一实际参数。
Promise()
构造子返回的Promise
对象,在要么resolveFunc
函数要么rejectFunc
函数被调用之时,它就成为“已决定”(resolved)的。要注意如果在调用这两个函数之一的时候,将另一个Promise
对象作为实际参数传递给它,则这个Promise
对象可以称为已决定但仍未落实。在执行器中有任何错误抛出,都会导致这个promise成为已拒绝状态,而返回值会被忽略。
Promise
类提供四个静态方法来实施异步任务并发,其中的Promise.allSettled()
方法,接受有多个promise的可迭代对象作为输入,当所有输入的promise都已落实之时,返回一个单一的已履行的Promise
对象,它决定出描述每个输入promise的结果的一个对象数组;其中每个对象都有status
属性,它是字符串"fulfilled"
或者"rejected"
;还有在status
为"fulfilled"
时出现的属性value
,或者在status
为"rejected"
时出现的属性reason
。
在下面例子中,全局fetch()
方法,启动从网络上取回一个资源的过程,并返回一旦响应可获得就履行的一个promise;这个promise决定出表示对这个请求响应的一个Response
对象,它只在遇到网络错误之时拒绝。Response.text()
方法返回一个promise,它决定出这个响应的主体的一个文本表示。
const urls = [
'https://developer.mozilla.org/',
'https://javascript.info/promise-api',
'https://no-such-url'
];
const fetchPromises = urls.map((url) => fetch(url));
Promise.allSettled(fetchPromises)
.then((results) => {
results.forEach((result, num) => {
if (result.status == "fulfilled") {
if (result.value.ok) {
result.value.text()
.then((data) => {
console.log(`${urls[num]}: ${data.length} bytes`);
})
.catch((err) => {
console.error(err);
});
}
else {
console.log(`${urls[num]}: ${result.value.status}`);
}
}
else if (result.status == "rejected") {
console.error(`${urls[num]}: ${result.reason}`);
}
});
});
JavaScript是天然的单线程的,所以在给定时刻只有一个任务在执行,但控制可以在不同的promise之间转移,使得多个promise表现为并发执行。在JavaScript中并行执行只能通过分立的worker后台线程来完成[106]。
同基于promise的代码合作的一种更简单的方式,是使用async/await关键字。在函数开始处增加async
,使其成为异步函数,异步函数总是返回一个promise。在异步函数中,可以在对返回一个promise的函数的调用之前,使用await
关键字;这使得代码在这一点上等待直到这个promise已落实下来,然后在这一点上promise的已履行的值被当作返回值,而已拒绝的理由被作为例外抛出。可以使用try...catch
块进行例外处理,就如同这个代码是同步的一样。
只读视图
在某些编程语言(如Oz、E和AmbientTalk)中,可以获得推迟值的“只读视图”,允许在解决(resolve)出这个值之后通过它来读取,但不允许通过它来解决这个值:
- 在Oz语言中,
!!
运算符用于获得只读视图。 - 在E语言和AmbientTalk中,推迟值由一对称为“promise/resolver对”的值表示。promise表示只读视图,需要resolver来设置推迟值。
- 在C++11中,
std::future
提供了一个只读视图。该值通过使用std::promise
直接设置,或使用std::packaged_task
或std::async
设置为函数调用的结果。 - 在Dojo Toolkit的1.5版本的Deferred API中,“仅限consumer的promise对象”表示只读视图。[107]
- 在Alice ML中,future提供“只读视图”[27],而promise包含future和解决future的能力[108]。
- 在.NET 4.0中,
System.Threading.Tasks.Task<T>
表示只读视图。解决值可以通过System.Threading.Tasks.TaskCompletionSource<T>
来完成。
对只读视图的支持符合最小特权原则,因为它使得设置值的能力仅限于需要设置该值的主体。在同样支持流水线的系统中,异步消息(具有结果)的发送方接收结果的只读promise,消息的目标接收resolver。
有关结构
“future”是事件同步原语的特例,它只能完成一次。通常,事件可以重置为初始的空状态,因此可以根据需要多次完成。[109]
“I-var”是具有下面定义的阻塞语义的future。它起源于Id语言中包含I-var的“I-structure”数据结构。可以使用不同值多次设置的有关同步构造称为“M-var”。M-var支持take
(采取)或put
(放置)当前值的原子性操作,这里采取这个值还将M-var设置回其初始的“空”状态。[110]
“并发逻辑变量”与future类似,但是通过合一更新,与逻辑编程中的“逻辑变量”相同。因此,它可以多次绑定到可合一的值,但不能设置回到空或未解决状态。Oz的数据流变量充当并发逻辑变量,并且还具有上面提到的阻塞语义。
“并发约束变量”是并发逻辑变量的一般化,以支持约束逻辑编程:约束可以多次“缩小”,表示可能值的较小集合。通常,有一种方法可以指定每当约束进一步缩小时应该运行的thunk;这是支持“约束传播”所必需的。
隐式与显式future
对future的使用可以是“隐式”的,任何对future的使用都会自动获得它的值,它就像是普通的引用一样;也可以是“显式”的,用户必须调用函数来获取值,例如Java中的Future[32]或CompletableFuture[33]的get
方法。获得一个显式的future的值可以称为“刺激”(stinging)或“强迫”(forcing)。显式future可以作为库来实现,而隐式future则通常作为语言的一部分来实现。
最初的Baker和Hewitt论文描述了隐式future,它们在演员模型和纯面向对象编程语言(如Smalltalk)中自然得到支持。Friedman和Wise的论文只描述了显式的future,可能反映了在老旧硬件上有效实施隐式future的困难。难点在于老旧硬件不能处理原始数据类型(如整数)的future。例如,add指令不会处理3 + future factorial(100000)
。在纯演员模型或面向对象语言中,这个问题可以通过向future factorial(100000)
发送消息+[3]
来解决,它要求future自己加3
并返回结果。请注意,无论factorial(100000)
何时完成计算,消息传递方法都可以工作,而且不需要任何“刺激”或“强迫”。
阻塞与非阻塞语义
如果future的值是异步访问的,例如通过向它发送消息,或者通过使用类似于E语言中的when
的构造显式地等待它,那么在消息可以被接收或等待完成之前,推迟直到future得到解决(resolve)是没有任何困难的。这是在纯异步系统(如纯演员语言)中唯一需要考虑的情况。
然而,在某些系统中,还可能尝试“立即”或“同步”访问future的值。这样的话就需要做出一个设计选择:
- 访问可能会阻塞当前线程或进程,直到future得到解决(可以具有超时)。这是Oz语言中“数据流变量”的语义。
- 尝试的同步访问总是会引发信号指示错误,例如抛出异常。这是E语言中远程promise的语义。[111]
- 潜在的,如果future已经解决,则访问可能成功,但如果未解决,则发出信号指示错误。这样做的缺点是引入了不确定性和潜在的竞争条件,这似乎是一种不常见的设计选择。
作为第一种可能性的示例,在C++11中 ,需要future值的线程可以通过调用wait()
或get()
成员函数来阻塞,直到它可获得为止。还可以使用wait_for()
或wait_until()
成员函数指定等待超时,以避免无限期阻塞。如果future对std::async
的调用,那么阻塞等待(没有超时)可能导致函数的同步调用以计算等待线程上的结果。
promise流水线
在分布式系统中使用推迟值可以显著地减少传输延迟。例如,指称推迟值的promise,成就了“promise流水线”[112][113],就像在E语言和Joule语言中实现的那样,它在Argus语言中称为“call-stream”[5]。
考虑一个涉及常规远程过程调用的表达式,例如:
t3 := (x.a()).c(y.b())
可以展开为
t1 := x.a(); t2 := y.b(); t3 := t1.c(t2);
每个语句需要发送一条消息,并在下一个语句可以继续之前收到一个答复。例如,假设x
、y
、t1
和t2
都位于同一台远程机器上。在这种情况下,在开始执行第三条语句之前,必须对该机器进行两次完整的网络往返。然后,第三条语句将引起另一个到同一个远程机器的往返。
上面的表达式可以使用E语言的语法写为:
t3 := (x <- a()) <- c(y <- b())
其中x <- a()
表示将消息a()
异步发送给x
。它可以展开为:
t1 := x <- a(); t2 := y <- b(); t3 := t1 <- c(t2);
所有三个变量都会立即为其结果分配promise,执行过程将继续进行到后面的语句。之后尝试解决t3
的值可能会导致传输延迟;但是,流水线操作可以减少所需的往返次数。如果与前面的示例一样,x
、y
、t1
和t2
都位于相同的远程机器上,则流水线实现可以用一次往返来计算t3
,不必用三次。由于所有三条消息都指向同一远程计算机上的对象,因此只需要发送一个请求,只需要接收一个包含结果的响应。另请注意,即使t1
和t2
位于不同机器上,或者位于与x
或y
不同的机器上,发送t1 <- c(t2)
也不会阻塞。
promise流水线应与并行异步消息传递区分开来。在支持并行消息传递但不支持流水线操作的系统中,上面示例中的消息发送x <- a()
和y <- b()
可以并行进行,但发送t1 <- c(t2)
将不得不等到t1
和t2
都被接收,即使x
、y
、t1
和t2
在同一个远程机器上。在涉及许多消息的更复杂情况下,流水线的相对传输延迟优势变得更大。
promise流水线操作也不应与演员系统中的流水线式消息处理相混淆,在这种系统中,演员可以在完成当前消息的处理之前,指定并开始执行下一个消息的行为。
有特定线程的future
某些语言比如Alice ML,定义的future可以关联着计算这个future值的特定线程[27]。这种计算可以通过spawn exp
,在创建future时及早地开始;或者通过lazy exp
,在首次需要其值时懒惰地开始。在延迟计算的意义上,懒惰的future类似于thunk 。
Alice ML还支持可由任何线程解决的future,并称谓它们为“promised future”[108]。这里的promise是给future的显式把柄(handle),它通过多态库函数Promise.promise
来创建,所有promise都关联的一个future,创建一个新promise也就创建了一个新鲜的future;这个future通过Promise.future
来提取,并且通过显式的应用Promise.fulfill
于对应的promise来消除,即在全局中将这个future替代为一个值。Alice ML不支持promise流水线,转而对于future,包括关联着promise的future,流水线是自然而然地发生的。
在没有特定线程的future(如Alice ML所提供的)中,通过在创建这个future的同时创建一个计算这个值的线程,可以直接实现及早求值的有特定线程的future。在这种情况下,最好将只读视图返回给客户,以便仅让新创建的线程能够解决这个future。
要在没有特定线程的future中,实现隐式惰性的有特定线程的future,需要一种机制来确定何时首次需要future的值(例如,Oz中的WaitNeeded
构造[114] )。
如果所有值都是对象,那么有实现透明转发对象的能力就足够了,因为发送给转发器的首条消息表明需要future的值。
假定系统支持消息传递,在有特定线程的future中,通过让解决线程向future自己的线程发送消息,可以实现没有特定线程的future。然而这可能被视为不必要的复杂性。在基于线程的编程语言中,最具表现力的方法似乎是提供一种混合:没有特定线程的future、只读视图、以及要么有WaitNeeded
构造要么支持透明转发。
传future调用
就求值策略而言,“传future调用”是非确定性的:future的值将在创建future和使用其值之间的某个时间进行求值,但确切的时间不确定的,一次运行和另一次运行的求值时间会不一样。计算可以在创建future时开始(及早求值),或者仅在实际需要值时开始(懒惰求值),并且可以在中途暂停,或在一次运行中执行。一旦future被赋值,它就不会在访问future的时候重新计算;这就像传需求调用时使用的记忆化。
“懒惰”future是确定性的具有惰性求值语义的future:future值的计算在首次需要时开始,与传需要调用一样。懒惰future使用在求值策略默认不是懒惰求值的语言中。例如,在C++11中,可以通过将std::launch::deferred
启动策略传递给std::async
以及计算值的函数来创建这种惰性future。
演员模型中的future语义
在演员模型中,形式为future <Expression>
的表达式,以它对具有环境E和客户C的Eval
消息的响应方式来定义:future表达式通过向客户C发送新创建的演员F(计算<Expression>
的响应的代理)作为返回值来响应Eval
消息,与之并发的向<Expression>
发送具有环境E和客户F的Eval
消息。F的默认行为如下:
- 当F收到请求R时,它会检查是否已经收到来自求值
<Expression>
的响应(可以是返回值或抛出异常),处理过程如下所示:- 如果它已经有了响应V,那么
- 如果V是返回值,那么向它发送请求R。
- 如果V是一个异常,那么就把这个异常抛给请求R的客户。
- 如果它还没有响应,那么将R存储在F内的请求队列中。
- 如果它已经有了响应V,那么
- 当F接收到来自求值
<Expression>
的响应V时,那么将V存储在F中,并且- 如果V是返回值,那么将所有排队的请求发送到V。
- 如果V是一个异常,那么就会把这个异常抛出给每个排队请求的客户。
但是,一些future可以通过特殊方式处理请求以提供更大的并行性。例如,表达式1 + future factorial(n)
可以创建一个新的future,其行为类似于数字1+factorial(n)
。这个技巧并不总是有效。例如,以下条件表达式:
if m>future factorial(n) then print("bigger") else print("smaller")
会挂起,直到factorial(n)
这个future已回应询问m
是否大于其自身的请求。
参见
引用
外部链接
Wikiwand - on
Seamless Wikipedia browsing. On steroids.