如何使用call / cc实现c#5.0中的新异步function?

我一直在关注有关c#5.0中新的asyncfunction的新公告。 我对继续传递样式以及新的c#编译器对Eric Lippert的post中的代码片段所做的转换有基本的了解:

 async void ArchiveDocuments(List 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); } } 

我知道有些语言通过call-with-current-continuation( callcc )本地实现continuation,但我真的不明白它是如何工作的或者它究竟是做什么的。

所以这就是问题:如果安德斯等人。 我决定咬紧牙关,只是在c#5.0中实现callcc而不是async / await特殊情况,上面的代码片段会是什么样子?

原始答案:

正如我所理解的那样,你的问题是“如果不是实施”等待“专门用于基于任务的异步,而是实现了与电流继续呼叫的更一般的控制流操作?”

好吧,首先让我们考虑一下“等待”的作用。 “await”采用Task类型的表达式,获取awaiter,并使用当前的continuation调用awaiter:

 await FooAsync() 

变得有效

 var task = FooAsync(); var awaiter = task.GetAwaiter(); awaiter.BeginAwait(somehow get the current continuation); 

现在假设我们有一个运算符callcc ,它将一个方法作为参数,并使用当前的continuation调用该方法。 这看起来像这样:

 var task = FooAsync(); var awaiter = task.GetAwaiter(); callcc awaiter.BeginAwait; 

换一种说法:

 await FooAsync() 

无非就是

 callcc FooAsync().GetAwaiter().BeginAwait; 

这是否回答你的问题?


更新#1:

正如评论者所指出的,下面的答案假设来自async / awaitfunction的“技术预览”版本的代码生成模式。 我们实际上在该function的测试版中生成了稍微不同的代码,但逻辑上它是相同的。 目前的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”之间的连接并不像预览版本那样简单,但我们仍然清楚地知道我们在awaiter的“OnCompleted”方法上做了一个callcc。 如果我们不需要,我们就不会做callcc。


更新#2:

作为这个答案

https://stackoverflow.com/a/9826822/88656

Timwi指出,call / cc和await的语义并不完全相同; 一个“真正的”调用/ cc要么我们“捕获”一个方法的整个延续, 包括它的整个调用栈 ,或者相当于整个程序被重写为延续传递样式。

“等待”function更像是“合作呼叫/ cc”; 延续只捕获“当前任务返回方法在等待点下一步做什么?” 如果任务返回方法的调用者在任务完成后要做一些有趣的事情,那么可以自由地将其继续作为任务的继续进行注册。

我不是延续的专家,但我会尝试解释async / await和call / cc之间的区别。 当然这个解释假定我理解call / cc和async / await,我不确定我这样做。 不过,这里……

使用C#’async’,您告诉编译器生成该特定方法的特殊版本,该方法了解如何将其状态置于堆数据结构中,因此可以“从实际堆栈中删除”并稍后恢复。 在async-context中,“await”就像“call / cc”,因为它使用编译器生成的对象来填充状态并离开“真正的堆栈”,直到任务完成。 但是,因为编译器重写了允许状态装箱的异步方法,所以只能在异步上下文中使用await。

在第一类调用/ cc中,语言运行库生成所有代码,使得当前延续可以装入一个call-continuation-function(使async关键字不必要)。 call / cc仍然像await一样,导致当前继续状态(想到堆栈状态)被淹没并作为函数传递给被调用函数。 一种方法是使用堆帧进行所有函数调用,而不是“堆栈”帧。 (有时也称为’无堆栈’,如’无堆栈python’或许多方案实现)另一种方法是从“真实堆栈”中删除所有数据并在调用call / cc目标之前将其填充到堆数据结构中。

如果在堆栈上混合调用外部函数(想想DllImport),则会出现一些棘手的问题。 我怀疑这是他们使用async / await实现的原因。

http://www.madore.org/~david/computers/callcc.html


因为在C#中,为了使用这些机制,必须将函数标记为“async”,我想知道这个异步关键字是否会成为一种病毒,它会扩散到许多库中的许多函数中。 如果发生这种情况 他们最终可能会意识到他们应该在VM级别实现一流的调用/ cc,而不是基于编译器重写的异步模型。 只有时间会给出答案。 但是,在当前的C#环境中,它肯定是一个有用的工具。

有点无意义我会说。 Scheme解释器通常使用状态机实现call / cc,该状态机将本地状态捕获到堆上。 这正是 C#5.0(或更正确的C#2.0迭代器)的作用。 他们确实实现了call / cc,他们提出的抽象非常优雅。