C#await vs continuations:不太一样?

Rom*_*kov 13 c# continuation

在阅读了Eric Lippert的回答之后,我得到了这样的印象,await并且call/cc几乎是同一枚硬币的两面,最多只有语法差异.然而,在尝试call/cc在C#5中实际实现时,我遇到了一个问题:要么我误解了call/cc(这是相当可能的),要么等待只是让人联想到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"
Run Code Online (Sandbox Code Playgroud)

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

Before
Result: stuff
Done
Run Code Online (Sandbox Code Playgroud)

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

但是,如果await在C#中使用,则调用continuation 不会还原此调用历史记录.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"
Run Code Online (Sandbox Code Playgroud)

因此,虽然C#5样式从未为我们提供传递值的延续对象,但整体上相似性非常惊人.除了这次,"After"永远不会被调用,这与真正的call/cc示例不同,这是爱C#并赞美其设计的另一个原因!

Tim*_*mwi 20

await确实不太一样call/cc.

call/cc您正在考虑的那种完全基本的确需要保存和恢复整个调用堆栈.但这await只是一个编译时的转换.它做了类似的事情,但没有使用真正的调用堆栈.

想象一下,你有一个包含await表达式的异步函数:

async Task<int> GetInt()
{
    var intermediate = await DoSomething();
    return calculation(intermediate);
}
Run Code Online (Sandbox Code Playgroud)

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

async Task<int> DoSomething()
{
    var important = await DoSomethingImportant();
    return un(important);
}
Run Code Online (Sandbox Code Playgroud)

现在考虑DoSomethingImportant()完成后会发生什么,并且结果可用.控制返回DoSomething().然后DoSomething()结束,然后会发生什么?控制返回GetInt().该行为是完全一样的,如果GetInt()是在调用堆栈.但事实并非如此; 你必须await每次通话时使用这种方式进行模拟.因此,调用栈被提升到在awaiter中实现的元调用栈中.

顺便提一下,同样如此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);
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我调用GetInts(),我得到的是一个封装当前执行状态的对象GetInts()(以便调用MoveNext()它恢复它停止的操作).这个对象本身包含通过迭代的迭代器GetStrings(),并呼吁MoveNext().因此,实际调用堆栈由对象层次结构代替,每次通过MoveNext()对下一个内部对象的一系列调用重新创建正确的调用堆栈.