exeを逆コンパイルして非同期メソッドの部分を見ると、IAsyncStateMachineインターフェースなるものを持った構造体が作られ、非同期メソッドまるっと全体がその構造体の「MoveNext」メソッドの中に移されて処理されているのがわかる。
MoveNextの処理の流れを表にしてみた。
1回目 (state = -1) | 2回目 (state = 0) |
||
---|---|---|---|
awaitの前 | コードを実行 | (すっとばす) | |
await Task | TaskからTaskAwaiterを得て 作業が完了しているか確認 |
||
完了 ↓ | 未完了 ↓ |
||
(すっとばす) | ・state= 0に変更 ・TaskAwaiterを 保存 ・非同期処理完了後に 自分が呼ばれるよう 登録 ↓ メソッドを抜ける |
||
(通らない) | TaskAwaiterの 復元 |
||
awaitの後 | コードを実行 (TaskのResultは TaskAwaiterから得る) | コードを実行 (TaskのResultは TaskAwaiterから得る) |
MoveNextが最初に呼ばれたとき、まず、awaitより前の行までを通常どおり実行した後、awaitが待つことになっているTaskからTaskAwaiterを得る。(以後の処理はTaskではなくTaskAwaiterを使って組み立てられている。)そして、TaskAwaiterが完了済みかどうかを確認する。
完了していたら、そのままawaitの後の行を実行する。(おしまい)
しかし完了していない場合は、ここでは終われない。まず構造体のstateフィールドの値を0(初期値は-1)にする。2周目にTaskAwaiterからResultを受け取るため、構造体のフィールドにTaskAwaiterを保存したのち、非同期作業完了後はもう一度自分(MoveNext)が呼ばれるよう登録する。こうして2周目の準備を終えたのち、処理を呼び出し元に返す。
(……そして同期コンテキストの他の処理が行われていく……)
どこか別の同期コンテキストで続いていた非同期作業が完了したようだ。登録していたMoveNextへの呼びかけがこちらの同期コンテキストに届く。再び活躍の場を得たMoveNextは、構造体のstateフィールドの値が0なのを見るや処理済みの工程をすっとばし、使い慣れたTaskAwaiterを取り戻して非同期処理の処理結果を手に入れると、残った仕事を手早く片づけた。構造体のstateを-2に変える。遠く別の同期コンテキストから届いた戻り値をAsyncTaskMethodBuilderに預け、MoveNextは目を閉じた。(めでたしめでたし)
ふざけすぎだ。が、特に反省はしていない。