C# 等待与延续 : not quite the same?

标签 c# continuation

看完Eric Lippert’s answer我的印象是 awaitcall/cc几乎是同一枚硬币的两面,最多只是句法上的差异。然而,在尝试实际实现时 call/cc在 C# 5 中,我遇到了一个问题:要么我误解了 call/cc(这很有可能),要么 await 只是 让人想起 call/cc。

考虑这样的伪代码:

function main:
    foo();
    print "Done"

function foo:
    var result = call/cc(bar);
    print "Result: " + result;

function bar(continuation):
    print "Before"
    continuation("stuff");
    print "After"

如果我对 call/cc 的理解是正确的,那么应该打印:

Before
Result: stuff
Done

至关重要的是,当继续被调用时,程序状态与调用历史一起恢复,所以foo返回到 main永远不会回到bar .

但是,如果使用 await 实现在 C# 中,调用延续不会恢复此调用历史记录。 foo返回到 bar ,并且没有办法(我可以看到)await可用于使正确的通话记录成为延续的一部分。

请解释:我是不是完全误解了call/cc的操作,或者是 await只是与call/cc不太一样?


既然我知道了答案,我不得不说有充分的理由认为它们非常相似。考虑上面的程序在伪 C#-5 中的样子:

function main:
    foo();
    print "Done"

async function foo:
    var result = await(bar);
    print "Result: " + result;

async function bar():
    print "Before"
    return "stuff";
    print "After"

因此,虽然 C# 5 风格从不给我们一个延续对象来传递一个值,但总的来说相似度是相当惊人的。除了这次很明显“After”永远不会被调用,这与 true-call/cc 示例不同,这是另一个喜欢 C# 并赞扬其设计的原因!

最佳答案

await 确实和call/cc 不太一样。

您正在考虑的那种完全基础的 call/cc 确实必须保存和恢复整个调用堆栈。但是 await 只是一个编译时转换。它执行类似的操作,但不使用真实 调用堆栈。

假设您有一个包含 await 表达式的异步函数:

async Task<int> GetInt()
{
    var intermediate = await DoSomething();
    return calculation(intermediate);
}

现在假设您通过 await 自身 调用的函数包含一个 await 表达式:

async Task<int> DoSomething()
{
    var important = await DoSomethingImportant();
    return un(important);
}

现在想想当 DoSomethingImportant() 完成并且其结果可用时会发生什么。控制返回到 DoSomething()。然后 DoSomething() 完成,然后会发生什么?控制返回到 GetInt()。如果 GetInt() 在调用堆栈上,则行为与 完全相同。但事实并非如此;您必须在希望以这种方式模拟的每个 调用中使用await。因此,调用堆栈被提升到在等待程序中实现的元调用堆栈。

顺便说一句,yield return 也是如此:

IEnumerable<int> GetInts()
{
    foreach (var str in GetStrings())
        yield return computation(str);
}

IEnumerable<string> GetStrings()
{
    foreach (var stuff in GetStuffs())
        yield return computation(stuff);
}

现在如果我调用 GetInts(),我得到的是一个对象,它封装了 GetInts() 的当前执行状态(以便调用 MoveNext () 在它停止的地方恢复操作)。此对象本身包含遍历 GetStrings() 并在 that 上调用 MoveNext() 的迭代器。因此,真正的 调用堆栈被对象层次结构所取代,这些对象层次结构每次都通过对下一个内部对象的 MoveNext() 的一系列调用来重新创建正确的调用堆栈。

关于C# 等待与延续 : not quite the same?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9826535/

相关文章:

c# - 我应该如何使用集合?

c# - 为什么 Java 和 C# 在 oops 上不同?

haskell - 让 IO 成为 MonadCont 的实例有意义吗?

c# - 如何在 C# 中获取当前的短日期时间格式?

c# - Unable install Xamarin Firebase - Common 32.961.0 to xamarin.forms解决方法

python - 如何限制 Python 中复杂行的长度?

azure - Windows Azure 存储表中的事务和延续 token

haskell - 为什么连续单子(monad)不能有 MonadFix 的实例?

c# - 如何在 WPF C# 中将按钮文本居中对齐?

haskell - 试图将 CPS 应用于口译员