14

Eric Lippertの答えを読んだ後、私は、ほとんど同じコインの両面であり、多くても構文上の違いがawaitあるという印象を受けました。call/ccしかし、実際にC#5で実装しようとするとcall/cc、問題が発生しました。call/ ccを誤解しているか(かなり可能です)、またはwaitは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です。

ただし、C#でを使用して実装されている場合await、継続を呼び出してもこの呼び出し履歴は復元されません。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スタイルでは、値を渡すための継続オブジェクトが提供されることはありませんが、全体的な類似性は非常に印象的です。今回は、true-call / ccの例とは異なり、「After」が呼び出されないことは明らかです。これは、C#を愛し、そのデザインを称賛するもう1つの理由です。

4

1 に答える 1

22

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()それを呼び出すイテレータが含まMoveNext()れてます。したがって、実際のMoveNext()呼び出しスタックは、次の内部オブジェクトへの一連の呼び出しを介して毎回正しい呼び出しスタックを再作成するオブジェクトの階層に置き換えられます。

于 2012-03-22T16:48:39.463 に答える