【C#】awaitの裏側

## awaitできるもの

`await` できるのは、`GetAwaiter` という名前のメソッドを持つ任意のオブジェクトです。
一段メソッドが挟まっており、さらに名前さえ一致すればどんなメソッドでもよいため、拡張性と柔軟性を持っています。
例えば、最もよく使われる`Task` の他にも、値型の`ValueTask` などが`await`することを想定した型として標準で用意されています。

`GetAwaiter` メソッドが返す値も、先程の`Awaiter`の条件を満たせば何でもよいです。具体的には

```cs
public class Awaiter<T> : System.Runtime.CompilerServices.INotifyCompletion
{
    // 条件1
    public bool IsCompleted { get; }

    // 条件2
    public void OnCompleted(Action action)
    {
    }

    // 条件3
    public T GetResult()
    {
    }
```

+ `System.Runtime.CompilerServices.INotifyCompletion` インターフェイスの実装
+ 実行が終了したかどうかを示す `IsCompleted` という名前のプロパティの実装
+ 実行が完了していないときに、完了後に実行する関数を登録できる ` OnCompleted` メソッドの実装
+ (実行が完了していない時は実行が完了するまで同期待ちし)結果を返す `GetResult` メソッドの実装

の４点です。

##メソッド呼び出しの抽象化

ここで一旦、メソッドを呼び出し、その結果を受け取るというプロセスについて考えてみましょう。
メソッドを呼び出すと、その呼び出しが完了したタイミングで制御フローが返り、存在する場合戻り値が得られます。
もう少し低レベルな、IL(中間言語)的な話をするならば、`call` 命令を実行すると、0個以上の戻り値が評価スタックにプッシュされ、その後 `call` 命令の次の命令が実行されます。(`callvirt`命令や`calli`命令でもこの点は同じです。)

ところで、制御フローすなわちリソースの利用権とは貴重なものです。特にGUIを持つアプリケーションでは、「UIスレッド」という固有の制御フローを皆で利用しないといけないのですから大変貴重です。しかし、メソッドの呼び出しが制御フローに依存している場合、例えば計算機資源が必要なのではなくネットワーク上での通信待ちが必要なのだとしても制御フローを保持せねばならず、この貴重な資源の効率的な利用を妨げてしまいます。

あるいは、メソッドの実行結果を評価スタックにプッシュするというのも厳しい制約です。評価スタックというIL上の概念は、実際にはCPUのレジスタである可能性もあるため、評価スタック上の値にはアドレスなどの概念はなく、呼び出された関数本人がその場で値を返すしか選択肢がないのです。これでは、メソッドの実行を別のスレッドや別のプロセスに委譲することができず、せっかくマルチコアプロセッサの時代であるのにUIスレッドを占有し続けることになっていまいます。

そこで、従来のメソッド呼び出しという概念を抽象化し、制御フローや評価スタックに依存しないような「新しいメソッド呼び出し」を考えてみましょう。
共有オブジェクトが存在して

1. 呼び出しが完了したかどうかが判別できる
2. 呼び出しが完了したあとに実行される命令を指定できる
3. 呼び出しの結果(戻り値)を受け取ることができる

の3つの条件が満たせるならば、従来 `return` や制御フローが担っていた機能を任せられます。
`Task` などの`await`可能な型は、同期/非同期を問わず、この3要件でメソッドを抽象化するともいえるのです。
そしてこの抽象化されたメソッドを呼び出すことを明示するのが `await` です。
この抽象化を採用したからといって非同期になるわけではありません。しかし、これによって結果返却を伴わずに`return`することが可能になり、結果が出る前に`return`せねばならない非同期が可能になるのです。

例えば、以下のような2つのメソッドがあったときに `Hoge()` と `await HogeAsync()` それぞれの違いは何でしょうか。
`await` をつけると非同期で実行される、、、とは限りません。

```cs
abstract class Sample
{
    // 実装はわからない
    public abstract int Hoge();
    public abstract Task<int> HogeAsync();
}

class Sample2 : Sample
{
    public override int Hoge()
    {
        System.Threading.Thread.Sleep(1000);
        return 1000;
    }

    public override async Task<int> HogeAsync()
    {
        System.Threading.Thread.Sleep(1000);
        return 1000;
    }
}
```

上記の `Sample2` クラスの `HogeAsync`は同期的に実行されます。それでも、`Hoge`は戻り値として直接に制御と値を返し、`HogeAsync`はTaskというオブジェクトを介して制御と値を返します。

## awaitの正体
このような抽象化されたメソッドを呼び出すのが`await`ですが、そのしくみはどうなっているでしょうか。
コンパイル結果的には、`await` はステートマシンを生成します。シンプルな `await` コードのコンパイル結果を覗いてみましょう。

```cs
await Task.Delay(100);
Console.WriteLine("waited");
```

このコードをコンパイルした結果を逆コンパイルしてみると

```cs
// Program.<<Main>$>d__0
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

[CompilerGenerated]
private sealed class <<Main>$>d__0 : IAsyncStateMachine
{
	public int <>1__state;

	public AsyncTaskMethodBuilder <>t__builder;

	public string[] args;

	private TaskAwaiter <>u__1;

	private void MoveNext()
	{
		int num = <>1__state;
		try
		{
			TaskAwaiter awaiter;
			if (num != 0)
			{
				awaiter = Task.Delay(100).GetAwaiter();
				if (!awaiter.IsCompleted)
				{
					num = (<>1__state = 0);
					<>u__1 = awaiter;
					<<Main>$>d__0 stateMachine = this;
					<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
					return;
				}
			}
			else
			{
				awaiter = <>u__1;
				<>u__1 = default(TaskAwaiter);
				num = (<>1__state = -1);
			}
			awaiter.GetResult();
			Console.WriteLine("waited");
		}
		catch (Exception exception)
		{
			<>1__state = -2;
			<>t__builder.SetException(exception);
			return;
		}
		<>1__state = -2;
		<>t__builder.SetResult();
	}

	void IAsyncStateMachine.MoveNext()
	{
		//ILSpy generated this explicit interface implementation from .override directive in MoveNext
		this.MoveNext();
	}

	[DebuggerHidden]
	private void SetStateMachine(IAsyncStateMachine stateMachine)
	{
	}

	void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
	{
		//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
		this.SetStateMachine(stateMachine);
	}
}
```

自動生成コードのためやや難解ですが、中核となる `MoveNext` メソッドの動作を簡単に説明すると、

1. `Task.Delay` を呼び出す。その戻り値を `GetAwaiter` して `Awaiter` を取得する。
2. `Awaiter` が終了状態か `IsCompleted` プロパティで判断する。終了状態なら、4へ飛ぶ。そうでなければ、完了時に自分自身を呼び出すよう `OnCompleted` メソッドで登録し、ステートマシンを待機状態に設定して `return` する。 
3. `Task.Delay` が完了し、`MoveNext` がコールバックされる。ステートマシンが待機状態なので、終了報告なのだとわかる。
4. 結果を受け取り、ステートマシンの状態をリセットする。
5. `Console.WriteLine` を呼び出す。  

同じ `MoveNext` を呼び出しても、内部の状態により実行されるコードが切り替わります。
また元々のコードの `await` 以後のコードはステートマシン内部に取り込まれてしまい、元の関数には残りません。

2 => 3 ではコールバック関数が発火する必要があるため、制御フローがイベントループまで戻っていることが必須です。
このことが、`await` が `async` でしか使えず...という`async/await`地獄を引き起こす原因の1つです。

そうは言っても、このように、「コンパイラがものすごく頑張る」ことによって、非同期を従来に近い書き味で書くことが可能になっているのです。