我一直在关注关于c# 5.0中新的async
特性的新公告。我对延续传递方式和新的c#编译器对Eric Lippert's post中的代码片段所做的转换有了基本的了解
async void ArchiveDocuments(List<Url> urls)
{
Task archive = null;
for(int i = 0; i < urls.Count; ++i)
{
var document = await FetchAsync(urls[i]);
if (archive != null)
await archive;
archive = ArchiveAsync(document);
}
}
我知道有些语言通过带有当前值的调用(callcc
)实现了本机延续,但我并不真正理解它是如何工作的,或者它到底做了什么。
所以问题来了:如果Anders等人。决定咬紧牙关,只在c# 5.0中实现callcc
,而不是async
/await
的特殊情况,那么上面的代码片段会是什么样子呢?
发布于 2010-11-02 00:42:52
原始答案:
根据我的理解,您的问题是“如果不是专门为基于任务的异步实现"await”,而是实现了更通用的call-with-current- control控制流操作,会怎么样?“
好吧,首先让我们考虑一下"await“是做什么的。"await“接受类型为Task<T>
的表达式,获取一个等待器,并使用当前的continuation调用该等待器:
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:
正如评论者指出的那样,下面的答案假设了异步/等待功能的“技术预览”版本中的代码生成模式。我们实际上在功能的测试版中生成了略有不同的代码,尽管在逻辑上是相同的。目前的codegen类似于:
var task = FooAsync();
var awaiter = task.GetAwaiter();
if (!awaiter.IsCompleted)
{
awaiter.OnCompleted(somehow get the current continuation);
// control now returns to the caller; when the task is complete control resumes...
}
// ... here:
result = awaiter.GetResult();
// And now the task builder for the current method is updated with the result.
请注意,这有点复杂,并且处理您正在“等待”已经计算出的结果的情况。如果你等待的结果实际上已经在内存中为你缓存了,那么就没有必要经历将控制权交给调用者的所有繁琐工作,并从你停止的地方重新开始。
因此,"await“和" callcc”之间的联系并不像在预览版中那样简单,但很明显,我们本质上是在等待器的"OnCompleted“方法上做一个callcc。如果没有必要,我们就不做callcc。
更新#2:
就像这个答案
https://stackoverflow.com/a/9826822/88656
Timwi指出,call/cc和await的语义并不完全相同;“真正的”call/cc要求我们“捕获”方法的整个延续,包括它的整个调用堆栈,或者等效地将整个程序重写为延续传递样式。
"await“特性更像是一个”协作调用/cc“;continuation只捕获”当前的任务返回方法在等待时下一步要做什么?“如果任务返回方法的调用者要在任务完成后做一些有趣的事情,那么它可以自由地将其complete注册为任务的complete。
发布于 2010-12-17 01:36:20
我不是延续方面的专家,但我将尝试解释async/await和call/cc之间的区别。当然,这个解释假设我理解call/cc和async/await,我不确定我是否理解。不管怎么说,这是……
使用C# 'async',你告诉编译器生成特定方法的一个特殊版本,该方法理解如何将它的状态封装到堆数据结构中,这样它就可以“从实际堆栈中移除”,并在以后恢复。在异步上下文中,"await“类似于"call/cc”,因为它使用编译器生成的对象来封装状态,并离开“实际堆栈”,直到任务完成。但是,因为它是对异步方法的编译器重写,允许将状态封存起来,所以await只能在异步上下文中使用。
在第一类call/cc中,语言运行时生成所有代码,以便可以将当前的continuation封装到call-continuation function中(使async关键字变得不必要)。call/cc仍然像await一样,导致当前延续状态(想想堆栈状态)被阻塞,并作为函数传递给被调用的函数。要做到这一点,一种方法是对所有函数调用使用堆框架,而不是“栈”框架。(有时称为“stackless”,如在“stackless python”或许多方案实现中)另一种方法是在调用call/cc目标之前从“真实堆栈”中移除所有数据,并将其填充到堆数据结构中。
如果堆栈中混合了对外部函数(比如DllImport)的调用,可能会出现一些棘手的问题。我怀疑这就是他们使用async/await实现的原因。
http://www.madore.org/~david/computers/callcc.html
因为在C#中,一个函数必须标记为" async“才能使用这些机制,我想知道这个async关键字是否会成为一种病毒,传播到许多库中的许多函数中。如果发生这种情况。他们可能最终意识到他们应该在VM级实现一流的call/cc,而不是这种基于编译器重写的异步模型。只有时间能证明一切。但是,在当前C#环境的上下文中,它无疑是一个有用的工具。
发布于 2010-11-01 23:43:25
我要说的是有点没意思。模式解释器通常使用状态机实现call/cc,该状态机将本地状态捕获到堆上。这也正是C# 5.0 (或者更准确地说,C# 2.0的迭代器)所做的事情。他们确实实现了call/cc,他们提出的抽象非常优雅。
https://stackoverflow.com/questions/4070237
复制相似问题