async/await で書いたコードと実行されるスレッド

#TL;DR

|   |await前|await|awai`後|
|:--|:-------:|:-----:|:-------:|
|動作スレッド|asyncメソッドの呼び出し元のスレッド|別スレッド|`SynchronizationContext.Current`のスレッド|
|呼び出し時点から見た同期状態|同期|非同期|非同期|

一般的なユースケースであろう、FormやUnityのUIスレッドでは`SynchronizationContext.current`にUIスレッドが設定されているので、UIスレッドからの呼び出しについては

|   |await前|await|await後|
|:--|:-------:|:-----:|:-------:|
|動作スレッド|UIスレッド|別スレッド|UIスレッド|
|呼び出し時点から見た同期状態|同期|非同期|非同期（後回し）|

``` cs:
public void OnClick (object sender, System.EventArgs e)
{
    // Thread#0
    HeavyAsync();
    // Thread#0
    Console.ReadKey(); // Thread#0
}

static async Task  HeavyAsync()
{
    // Thread#0
    await Task.Delay(1000); // Thread#1
    // Thread#0
    await Task.Delay(1000); // Thread#1
}
```

# `await`すると何が起こるか

```cs
await Task.Delay(300);
Console.WriteLine("After");
```
というように書いたとします。すると、意味としては次のようになるようです

```cs
var context = SynchronizationContext.Current; //Thread#a
Task.Delay(300).ContinueWith(
    _=>context.Post(_=> // contextのThread、Thread#aにタスクをさせる
        Console.WriteLine("After")
    )
);
```
つまり、`await`もその後もその場で実行されることはありません。最初のawaitが出てきた時点でasyncメソッドの呼び出し元に戻ります。
しかし、`SynchronizationContext.Current`に呼び出だし元のスレッドが入っている場合、awaitの後は呼び出し元に戻ります。戻るといっても即時実行されるわけではなく、タスクとして後回しにされます。

# 使い道

どうせ後回しにするなら、「別スレッドでそのまま処理した方がいいじゃない」と思いそうですが、UIスレッドでしかアクセスできないクラスが結構あります。（スレッドセーフでないため）そう考えると、言語仕様は次のような使われ方を想定してるんだなと合点がいきます。

```cs
Label label; // UIのコントロールで、UIスレッドでしかアクセス不可
public aysnc Task OnClick (object sender, System.EventArgs e)
{
    var content = await LoadFromFileAsync();
    label.text = content; // ここは後回しとは言えどUIスレッドなのでOK
}
```

これを`await`使わずに書こうとすると…

```cs
Label label; // UIのコントロールで、UIスレッドでしかアクセス不可
public void OnClick (object sender, System.EventArgs e)
{
    var context = SynchronizationContext.Current;
    LoadFromFileAsync().ContinueWith(
        content=>context.Post(content=>
            label.text = content;
            // ポイントは、「LoadFromFileの直後に回される」のではなく、
            // 「LoadFromFileの直後に『UIスレッドが暇なときに回される』という処理を回す」
            // ということで、コードの雰囲気とは若干タイミングがずれている
    ));
}
```

`await`の方が処理の一見みやすい気がしますが、ここまでのことをちゃんと理解していないと、実行順序が思わぬものになってしまいますね。この例のような単純なものでは全く意識せずともうまくいくと思いますが、メンバの呼び出し順序が厳しいクラスを使っていたり、なまじ昔ながらのスレッドをかじっていると混乱しそうです。（しました）

# コンソールアプリなどの場合

自分で設定しない限り`SynchronizationContext.Current`は`null`なので、呼び出し元スレッドに後回しで戻そうにも戻れません。他のスレッドに回されます。
コンソールアプリでは、メインスレッドでないと出来ないようなことはGUIアプリほどないので、awaitの旨味もあまりないですね。

# 検証

### Formアプリ

```csharp
void OnClic(EventArgs e){
    Console.WriteLine("1) "+Thread.CurrentThread.ManagedThreadId);
    HeavyAsync();
    Console.WriteLine("Hello World!");
    Console.ReadKey();
}

static async Task  HeavyAsync()
{
    int c = 2;
    for(int i=0;i<3;i++){
        Console.WriteLine(c++ +") "+Thread.CurrentThread.ManagedThreadId);
        long time = DateTime.Now.Ticks;
        while(DateTime.Now.Ticks < time + 3000000) { }
        Console.WriteLine(c++ +") "+Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(300);   
    }
    Console.WriteLine("Heavy end!");
}
```

```:console
1) 1
2) 1
3) 1
Hello World!
 <- ReadKeyの入力のEnter（押すまでブロックされてる）
<- OnClickは抜けた
4) 1  <- 後回しにされてる
5) 1  <- 後回しにされてる
6) 1  <- 後回しにされてる
7) 1  <- 後回しにされてる
Heavy end!  <- 後回しにされてる
```

### コンソールアプリ

```csharp
static void Main(string[] args)
{
    Console.WriteLine("1) "+Thread.CurrentThread.ManagedThreadId);
    HeavyAsync();
    Console.WriteLine("Hello World!");
    Console.ReadKey();
}

static async Task  HeavyAsync()
{
    int c = 2;
    for(int i=0;i<3;i++){
        Console.WriteLine(c++ +") "+Thread.CurrentThread.ManagedThreadId);
        long time = DateTime.Now.Ticks;
        while(DateTime.Now.Ticks < time + 3000000) { }
        Console.WriteLine(c++ +") "+Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(300);   
    }
    Console.WriteLine("Heavy end!");
}
```

```:console
1) 1
2) 1
3) 1
Hello World!
4) 4  <- Mainはブロックされたままだが、別スレッドで実行されてる
5) 4
6) 5
7) 5
Heavy end!
<- ReadKeyはまだブロッキングを続けている
```

FormアプリではUIスレッドがブロックされている間、`await`の後の処理がされていないのに対し、コンソールアプリではされています。`SynchronizationContext.Current`がUIスレッドになっているか、`null`になっているかの違いです。

# asyncメソッド呼び出し時、すぐにスレッドが切り替わって欲しい場合

```csharp
Task.Run(HeavyAsync);
```

```:console
1) 1
Hello World!
2) 3 <- ReadKeyしてるけど関係ない
3) 3
4) 4
5) 4
6) 4
7) 4
Heav end!
<- まだReadKeyがブロックしてる
```
即座に切り替わっています。最早、呼び出しスレッドは関係なくなります。