以下の具体的な質問にお答えしますが、私たちがどのように歩留まりと待機を設計したかについての私の広範な記事を読んだほうがいいでしょう。
https://blogs.msdn.microsoft.com/ericlippert/tag/continuation-passing-style/
https://blogs.msdn.microsoft.com/ericlippert/tag/iterators/
https://blogs.msdn.microsoft.com/ericlippert/tag/async/
これらの記事の一部は現在古くなっています。生成されるコードは多くの点で異なります。しかし、これらは確かにそれがどのように機能するかという考えをあなたに与えるでしょう。
また、ラムダがクロージャクラスとして生成される方法を理解していない場合は、まずそれを理解してください。ラムダを下げない場合、非同期の表と裏を作成することはできません。
待機時間に達すると、ランタイムはどのコードを次に実行する必要があるかをどのようにして知るのでしょうか。
await
次のように生成されます:
if (the task is not completed)
assign a delegate which executes the remainder of the method as the continuation of the task
return to the caller
else
execute the remainder of the method now
それは基本的にそれです。Awaitは単なるファンシーなリターンです。
いつ中断したところから再開できるか、どのようにしてそれをどこで覚えているか?
さて、どうやってそれをせずに待たか?メソッドfooがメソッドbarを呼び出すとき、fooのアクティブ化のすべてのローカルが何のバーに関係なく、fooの中央に戻る方法を覚えています。
あなたはそれがアセンブラでどのように行われるか知っています。fooのアクティベーションレコードがスタックにプッシュされます。ローカルの値が含まれています。呼び出しの時点で、fooの戻りアドレスがスタックにプッシュされます。barが完了すると、スタックポインターと命令ポインターは必要な場所にリセットされ、fooは中断したところから続行します。
待機の継続はまったく同じですが、アクティブ化のシーケンスがスタックを形成しないという明白な理由でレコードがヒープに書き込まれます。
タスクの継続として待機するデリゲートには、(1)次に実行する必要のある命令ポインターを与えるルックアップテーブルへの入力である数値、および(2)ローカルと一時のすべての値が含まれます。
そこにはいくつかの追加のギアがあります。たとえば、.NETでは、tryブロックの途中に分岐することはできません。そのため、tryブロック内のコードのアドレスを単にテーブルに貼り付けることはできません。しかし、これらは簿記の詳細です。概念的には、アクティブ化レコードは単にヒープに移動されます。
現在のコールスタックはどうなりますか?どういうわけか保存されますか?
現在のアクティベーションレコードの関連情報がスタックに最初に置かれることはありません。get-goからヒープ外に割り当てられます。(まあ、仮パラメーターは通常スタックまたはレジスターに渡され、メソッドの開始時にヒープ位置にコピーされます。)
呼び出し元のアクティベーションレコードは保存されません。待ちはおそらく彼らに戻るだろう、覚えておいてください、そうすれば彼らは普通に扱われるでしょう。
これは、awaitの単純化された継続渡しスタイルと、Schemeなどの言語で見られる真のcall-with-current-continuation構造との間の密接な違いであることに注意してください。これらの言語では、呼び出し元への継続を含む継続全体がcall-ccによってキャプチャされます。
呼び出しメソッドが待機する前に他のメソッド呼び出しを行うとどうなりますか?スタックが上書きされないのはなぜですか?
これらのメソッド呼び出しが返されるため、待機の時点でそれらのアクティブ化レコードはスタックに存在しなくなります。
そして、例外が発生してスタックが解放された場合、ランタイムはどのようにしてこれらすべてを処理しますか?
キャッチされない例外が発生した場合、例外はキャッチされ、タスク内に格納され、タスクの結果がフェッチされると再スローされます。
前に述べた簿記を覚えていますか?例外のセマンティクスを正しく取得するのは大変な苦労でした。
収量に達した場合、ランタイムは、物事をピックアップする必要があるポイントをどのように追跡しますか?イテレータの状態はどのように保存されますか?
同じ方法。ローカルの状態はヒープに移されMoveNext
、次に呼び出されたときに再開する必要がある命令を表す番号がローカルとともに格納されます。
繰り返しになりますが、例外が正しく処理されることを確認するために、イテレーターブロックにはたくさんのギアがあります。