原始回答:
据我理解,您的问题是:“如果不是为了实现基于任务的异步性而专门实现'await',而是实现了更一般的控制流操作'call-with-current-continuation',那会怎样?”
首先,让我们考虑一下"await"的作用。 "await" 接受类型为 Task<T>
的表达式,获取一个awaiter,并使用当前 continuation 调用 awaiter:
await FooAsync()
变得有效
var task = FooAsync();
var awaiter = task.GetAwaiter();
awaiter.BeginAwait(somehow get the current continuation);
现在假设我们有一个名为callcc
的操作符,它以一个方法作为参数,并使用当前的继续来调用该方法。代码如下:
var task = FooAsync();
var awaiter = task.GetAwaiter();
callcc awaiter.BeginAwait;
换句话说:
await FooAsync()
不过是
callcc FooAsync().GetAwaiter().BeginAwait;
那回答了你的问题吗?
更新 #1:
正如评论者所指出的,下面的答案假定从“技术预览”版本的async/await功能中生成代码模式。我们实际上在该功能的beta版中生成略有不同的代码,但逻辑上是相同的。目前的代码生成大致如下:
var task = FooAsync();
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
awaiter.OnCompleted(somehow get the current continuation);
}
result = awaiter.GetResult();
请注意,这种方式有些复杂,适用于你正在“等待”已经计算出来的结果的情况。如果你正在等待的结果实际上已经在内存中缓存了,那么就没有必要经过所有的操作,将控制权交给调用者,然后在离开的地方重新开始,因为该结果已经可以直接使用。
因此,“await”和“callcc”之间的联系并不像预发布版中那样简单,但仍然清楚,实质上我们是在awaiter的“OnCompleted”方法上进行callcc。只有在必要时才执行callcc操作。
更新#2:
正如Timwi在这个答案(https://dev59.com/Y2kw5IYBdhLWcg3wirCs#9826822)中指出的那样,“call/cc”和“await”的语义并不完全相同;一个“真正”的call/cc要求我们捕获方法的整个续体,包括其整个调用堆栈,或等价地,整个程序被重写成续传风格。
“await”功能更像是一种“协作式的call/cc”;续体仅捕获“当前任务返回方法即将在await点做什么?” 如果调用任务返回方法的呼叫者在任务完成后要执行一些有趣的动作,那么它可以自由地将其续体注册为任务的续体。
await
帮助实现了 CPS(continuation passing style)风格。即使将整个程序重写为 CPS 风格,语义上仍与call/cc
有很大不同:你仍然无法获得一个对象(即 continuation),通过它传递参数来返回原始call/cc
调用的结果。 - Roman Starkov