C#等待VS延续:不太一样? [英] C# await vs continuations: not quite the same?

查看:221
本文介绍了C#等待VS延续:不太一样?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

埃里克利珀的回答我得到了的await 和呼叫/立方厘米是同一枚硬币的几乎两侧,与大多数语法上的不同。然而,在试图真正实现用C 呼叫/立方厘米#5,我遇到了一个问题:无论是我误解了呼叫/立方厘米(这是相当可能的),或者等待只让人想起的呼叫/立方厘米

After reading Eric Lippert’s answer I got the impression that await and call/cc are pretty much two sides of the same coin, with at most syntactic differences. However, upon trying to actually implement call/cc in C# 5, I ran into a problem: either I misunderstand call/cc (which is fairly possible), or await is only reminiscent of 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"

如果我的电话的理解/立方厘米是正确的,那么这应该打印:

If my understanding of call/cc is correct, then this should print:

Before
Result: stuff
Done

重要的是,当继续被调用时,该程序状态被恢复的连同呼叫历史的,以便返回到永不回来

Crucially, when the continuation is called, the program state is restored along with the call history, so that foo returns into main and never comes back to bar.

不过,如果使用等待在C#中,调用延续的恢复该通话记录实施。 返回到,而且也没有办法(我可以看到)的的await 可用于制作延续了正确的呼叫历史的一部分。

However, if implemented using await in C#, calling the continuation does not restore this call history. foo returns into bar, and there’s no way (that I can see) that await can be used to make the correct call history part of the continuation.

请解释一下:我才完全错误理解<$ C的操作$ C>呼叫/立方厘米,或等待只是不太一样呼叫/立方厘米

Please explain: did I completely mis-understand the operation of call/cc, or is await just not quite the same as call/cc?

现在我知道答案,我不得不说,有一个很好的理由认为它们是相当类似。考虑上面的程序是什么样的伪C#-5:

Now that I know the answer, I have to say that there’s a good reason to think of them as fairly similar. Consider what the above program looks like in pseudo-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的风格从来没有给我们一个延续对象传递价值,整体的相似性是相当惊人的。只不过这一次它是完全明显的后不会被调用,不像真正的呼叫/ cc的例子,这是另一种无缘无故的爱C#和赞美它的设计!

So while the C# 5 style never gives us a continuation object to pass a value to, overall the similarity is quite striking. Except that this time it’s totally obvious that "After" never gets called, unlike in the true-call/cc example, which is another reason to love C# and praise its design!

推荐答案

的await 确实不太一样呼叫/立方厘米

那种完全根本呼叫/立方厘米你所想的的确必须保存和恢复整个调用堆栈。但等待只是一个编译时转换。它有类似的功能,但不使用的真正的调用栈

The kind of totally fundamental call/cc that you are thinking of would indeed have to save and restore the entire call stack. But await is just a compile-time transformation. It does something similar, but not using the real call stack.

假设你有包含的await表达式中异步功能:

Imagine you have an async function containing an await expression:

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

现在想象的功能,您通过的await <打电话/ code>的本身的包含的await 表达式:

Now imagine that the function you call via await itself contains an await expression:

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

现在想想会发生什么,当 DoSomethingImportant()饰面和它的结果是可用的。控制返回到 DoSomething的()。然后 DoSomething的()处理和什么然后发生了什么?控制返回到调用getInt()。该行为是完全一样的的如调用getInt()是调用堆栈上。但它是不是真的;你必须使用等待每个的调用您希望模拟这种方式。因此,调用堆栈抬起成在awaiter实施荟萃调用堆栈。

Now think about what happens when DoSomethingImportant() finishes and its result is available. Control returns to DoSomething(). Then DoSomething() finishes and what happens then? Control returns to GetInt(). The behaviour is exactly as it would be if GetInt() were on the call stack. But it isn’t really; you have to use await at every call that you want simulated this way. Thus, the call stack is lifted into a meta-call-stack that is implemented in the awaiter.

同样的,顺便说一句,的真回报收益率

The same, incidentally, is true of 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)迭代(和调用迭代的MoveNext()上的的。因此,的真正的调用栈是由重新创建正确的呼叫通过一系列调用到的MoveNext()每次栈对象的层次结构取代在接下来的内部对象。

Now if I call GetInts(), what I get back is an object that encapsulates the current execution state of GetInts() (so that calling MoveNext() on it resumes operation where it left off). This object itself contains the iterator that is iterating through GetStrings() and calls MoveNext() on that. Thus, the real call stack is replaced by a hierarchy of objects which recreate the correct call stack each time via a series of calls to MoveNext() on the next inner object.

这篇关于C#等待VS延续:不太一样?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆