在阅读了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()对下一个内部对象的一系列调用重新创建正确的调用堆栈.
| 归档时间: |
|
| 查看次数: |
2528 次 |
| 最近记录: |