2012-03-22 3 views
11

Eric Lippert’s answerを読んだあと、awaitcall/ccはほとんどの構文上の違いを除いて同じコインのほぼ2つの側面であるという印象を受けました。しかし、実際にC#5にcall/ccを実装しようとすると、私はcall/cc(これはかなり可能です)を誤解していたか、またはというコール/ ccを思い出してしまいます。C#は継続性を待っています:全く同じではありませんか?

は、このような擬似コードを考えてみましょう:

function main: 
    foo(); 
    print "Done" 

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

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

コール/ ccでの私の理解が正しければ、これは印刷する必要があります:

Before 
Result: stuff 
Done 

継続が呼ばれる極めて重要な、プログラム状態はと一緒にに復元され、foomainに戻り、barに戻ることはありません。

しかし、C#でawaitを使用して実装されている場合、継続を呼び出すとは、この通話履歴を復元しません。 foobarに戻って、awaitが正しい通話履歴の一部を継続するために使用できる方法はありません(私には分かります)。

説明してください:私はcall/ccの操作を完全に誤解しましたか?awaitcall/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スタイルは、全体的な類似性が非常に印象的で、に値を渡すために私たちに継続オブジェクトを与えることはありませんしながら。この時間を除いて、C#を愛し、そのデザインを賞賛する別の理由である、本当のコール/ ccの例とは違って、 "After"が呼び出されないことは全く明らかです!

+0

Timwiは正しいです。 awaitは "local effect" call/ccのようなものです。これは私が元の答えで呼ぶとは思わなかった微妙なことです。私はそれを更新しました。 –

答えて

18

awaitは実際には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()を呼び出す場合さて、私は戻って取得すると、それにMoveNext()を呼び出すと、再開するようGetInts()の現在の実行状態を(カプセル化するオブジェクトでありますそれが途絶えたところでの操作)。このオブジェクト自体には、GetStrings()を反復し、MoveNext()を呼び出すイテレータが含まれています。したがって、のリアル呼び出しスタックは、次の内部オブジェクトの一連の呼び出しによって毎回正しい呼び出しスタックを再作成するオブジェクトの階層に置き換えられます。MoveNext()

+0

なお、非同期をデバッグするときは、ブレークポイントのメソッド名としてMoveNextが表示されます。つまり、IEnumerableのイテレータステートマシンジェネレータを使用しました。 –

関連する問題