我一直在关注有关将在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)在本地实现延续的,但是我并不真正了解这种方式的工作原理或确切的作用。

所以这是一个问题:如果安德斯等人。已经决定硬着头皮,只在C#5.0中实现callcc而不是async/await特殊情况,上面的代码段是什么样的?

最佳答案

原始答案:

据我所知,您的问题是“如果不是专门针对基于任务的异步实现“等待”,而是实现了更为通用的随流连续调用控制流操作,该怎么办?”

好吧,首先让我们考虑一下“等待”的作用。 “await”采用类型Task<T>的表达式,获取一个等待者,并使用当前继续符来调用该等待者:

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:

正如评论者所指出的那样,下面的答案假定来自异步/等待功能的“技术预览”版本的代码生成模式。尽管在逻辑上是相同的,但实际上我们在该功能的Beta版本中生成的代码略有不同。当前的代码生成类似于:
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的语义并不完全相同。 “真正的”调用/cc要求我们“捕获”方法的整个连续性,包括其整个调用堆栈,或者等效地是将整个程序重写为连续传递样式。

“等待”功能更像是“合作调用/抄送”;继续仅捕获“在等待时下一步将要执行的当前任务返回方法是什么?”如果任务完成后任务返回方法的调用者打算做一些有趣的事情,则可以自由地将其继续注册为任务的继续。

10-07 22:42