C# 今更ですが、await / async

# はじめに

今更すぎて、この記事自体存在価値がなさそう…

C#のスレッド(非同期処理)は、他の言語では見ない類稀な構文であるため、かなり難しいと思います。さらに`await` / `async`を使うと、プログラムに書かれていない部分にも意識を回す必要があり、初心者には厳しい関門でしょう。スレッド自体の考え方やできることは言語によらずあまり変わらないのですが、C#での表現方法(文法)が独特であるのと、GUIプログラミングという鬼門の分野があるからでしょう。

# スレッドを理解するために、押さえておきたいポイント

文法やプログラムの例から始めようと思ったのですが、スレッドを理解するには、その考え方や注意点などプログラムの裏側にあるものを知らなければならないと思うので、まず理解する上で押さえておくべきポイントをを上げたいと思います。

ポイント１：スレッドをどう起動するのか？
ポイント２：スレッドの終了をどう知るのか？
ポイント３：スレッドの実行結果をどう受け取るのか？

この３つはいいと思います。ただ私は、次の４つ目のポイントがスレッドを使えるようになる上で一番重要であるように思います。

ポイント４：どのスレッドで処理が実行されているのか？

どのスレッドで実行しているのかが良く分からないので、とりあえずスレッドの処理が終わるまで待機しておけばいいとか（スレッドを分かっている人から怒られる）、不用意なミスをするとか（デッドロック）、してしまうように思います。この体験が「スレッドには近寄らない方がいい」という気持ちが発生し、ますますスレッドが苦手、分からないという悪循環になっているように思います。

# ポイント１：スレッドをどう起動するのか？

まず、なぜスレッドを使いたいのでしょうか？　おそらく時間が掛かる処理を別スレッドで実行して、メインスレッドの処理をじゃましないようにするためでしょう。たとえば、ボタンを押したらアプリケーションがフリーズしないように、子スレッドに処理をさせてメインスレッドは即リターンする、などでしょうか。つまりスレッドで実行したいのは処理であり、そしてプログラムで ～処理を見たら、ほぼメソッドで表現されます。ですから、スレッドで処理をするメソッドをまず作りましょう。例として 1 から 100 まで足した結果(5050)を返すメソッドにします。

```csharp
int Calculate()
{
    int total = 0;
    for (int i=1; i<=100; ++i)
        total += i;
    Thread.Sleep(4560); // 何か重い処理をしている...
    return total
}
```

100回足し算をするだけでは今のコンピューターは一瞬で処理が終わってしまうので、何か時間が掛かる処理として `Thread.Sleep()` を入れておきます。スレッドの説明であるあるです。

これを子スレッドで実行したい訳ですから、こんな風にメソッドを引数にとる書き方ができるとよさそうです。（「メソッドを引数にとる」という言い方は本当は正しくなく、実際には `Action` や `Func<>` が引数です。）

```csharp

    子スレッドで実行する(Calculate);
```

C#ではこんな風に１行で書ける構文があるのでしょうか？　それがあるんですね。それが`Task.Run()`です。

```csharp
    Task<int> task = Task.Run<int>(new Func<int>(Calculate));
```

残念ながらC#はメソッド名をそのまま書けないので、`Func<int>`で包んであげます（追記：多分一番良いのは、 `Task.Run(() => Calculate())`とラムダ式で書くことだと思います）。また、今は説明のため型を明記していますが、実際のプログラムでは型推論が働き、型を省略（`Run<int>`の`<int>`の部分）できます。戻り値の `Task<int> task =` の部分はすぐ後で説明します。

C#の場合、メソッドをインライン化（＝ラムダ式）できるので、こう書くこともできます。よく見る例はこっちかもしれません。

```csharp
    Task<int> task = Task.Run<int>(() => {
        int total = 0;
        for (int i=1; i<=100; ++i)
            total += i;
        Thread.Sleep(4560); // 何か重い処理をしている...
        return total
    });
```

いずれにせよ、これでスレッドを起動し、処理を開始させることができました。

# ポイント２：スレッドの終了をどう知るのか？、ポイント３：スレッドの実行結果をどう受け取るのか？

C#ではこの２つはほぼ同時にプログラムで表現されるので、１つにまとめました。

さて、ポイント１でスレッドを起動したのですから、次はスレッドの処理の結果をもらうやり方です。`Run()`の戻り値がスレッドの処理の結果　…ではないです。スレッドの処理の戻り値は、スレッドに投入したメソッドの戻り値のことですから、`int`です。しかし`int`がもらえるのはスレッドの処理が完了した後なので、すぐに `int` がもらえるとおかしいのです。だから **`Run()` の戻り値は、スレッドの処理が完了したときにもらえる予定の `Task<int>` になる**のです。いわゆる引換券みたいなものです。

では、実際のスレッドの戻り値の取り方はどうやるのでしょうか。それは `Task` の `Result`プロパティで取得できます。**`Result`プロパティを使うと、スレッドの処理が「完了」するまで待って、結果を取得**できます。

```csharp
    Task<int> task = Task.Run<int>(new Func<int>(Calculate));
    int result = task.Result; // スレッドの終了まで「待つ」
```

ただしこう書くと、スレッドが終了するまで**待つ**ことになります。`Run()`で子スレッドに処理を投入したのに、すぐ次の行でスレッドの処理が終了するまで待ってしまっては、非同期処理の利点を完全に殺していることになります。つまり、まったくの無駄です。非同期処理なのですから、メインスレッドは`Run()`で処理を子スレッドに投入した後、スレッドの完了を待たずさっさと自分の処理に戻りたいのです。そんな都合の良い構文がC#にはあるのでしょうか？　まあこう書いている地点であるんですけどね。

```csharp
   Task<int> task = Task.Run<int>(new Func<int>(Calculate));
   int result = await task; // スレッドの処理の結果を「待ち受け」する
```

**`Task`に`await`を付けると、メインスレッドは処理を即リターン**して、**子スレッドの処理が終了すると、`int result`以降の処理が、おもむろに再開**します。何かすごくご都合主義を感じますが、こう書けてしまうのです。`await`の動作は、他の記事でも書かれている通り、`yield return`に似ています。また、`await`について１点注意しておきたいのは、`await`はスレッドの処理が終わるまで待たないで**待ち受け**をしている、ということです。

ちなみにこの記述は２行に分けなければならない訳ではなく、１行にできます。

```csharp
    int result = await Task.Run<int>(new Func<int>(Calculate));
```

これでスレッドの終了通知（`await`を書いておけば勝手にやってくれる）とスレッドの実行結果（`await`で`Task`から取り出せる）は出来た訳ですが、今までコード断片だったので、もう少しちゃんと書きましょう。このスレッドへの投入を、ボタンクリックで実行するとしましょう。つまりボタンのイベントハンドラで呼び出すとします。スレッドの結果は `TextBox` コントロールの `text` へ設定することにします。

```csharp
    button1.Click += button1_ClickedAsync;
```

```csharp
public async void button1_ClickedAsync(object sender, EventArgs e)
{
    Task<int> task = Task.Run<int>(() => {
        int total = 0;
        for (int i=1; i<=100; ++i)
            total += i;
        Thread.Sleep(4560); // 何か重い処理をしている...
        return total
    });
    int result = await task;
    this.text.Text = $"{result}"; // 雑なstring変換
}
```

メソッド内に`await`があるということは、すなわちスレッド実行があるということであり、すなわち非同期処理をするということになります。C#では非同期処理をするメソッドに、`async`キーワードを付けるというルールがあります。**`async`があると、呼び出し側は`await`を付けなければならない**という、暗黙ルールを伝えることになります。今回の例は、イベントハンドラにしちゃったので `await` が不要ですが…　正直例としてはイマイチでした。またメソッド名は末尾に `Async` を付けるという慣習があります（ルールじゃない）。

# ポイント４：どのスレッドで処理が実行されているのか？

`await` / `async` の文法だけならポイント３までで完了ですが、ポイント４もちゃんと見ておきましょう。

例として、GUIアプリケーションでボタンを押したらスレッドを起動するプログラムにしてみます。単にスレッドを起動して、結果を`await`で待ち受けてテキストボックスに設定するだけです。どのスレッドで実行しているかは、`Thread.CurrentThread.ManagedThreadId`でスレッドIDが取れるので、これで分かります。

```csharp:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click START");
		Task<int> task = Task.Run<int>(new Func<int>(Calculate));
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click Before");
		int result = await task;
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click After");
		this.textBox1.Text = $"{result}";
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click END");
	}

	private int Calculate()
	{
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate START");
		int total = 0;
		for (int i=0; i<=100; ++i)
			total += i;
		Thread.Sleep(4560);
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate END");
		return total;
	}
}
```

実行結果

> 9: button1_Click START
> 9: button1_Click Before
> 10: Calculate START
> 10: Calculate END
> <strong>9</strong>: button1_Click After
> <strong>9</strong>: button1_Click END

`await`後の処理は、親スレッド（ID=9）で実行されているのが分かると思います。絵で表すと次のようになります。（<font color='orange'>オレンジ</font>が親スレッド、<font color='green'>緑</font>が子スレッド）

![2017-11-28_151331.png](https://qiita-image-store.s3.amazonaws.com/0/208281/f7b3f486-0670-1d3a-c7bf-94d6c83c458f.png)

**`await`後の処理をするスレッドは、そのメソッドを実行したスレッド、すなわちこの例の場合<font color='orange'>親スレッド</font>で実行される**というルールがあります。<font color='green'>子スレッド</font>のままではありません。一般的な説明では、`await`後の処理は、メソッドを実行した同期コンテキスト(synchronization context)で実行される、とか同期コンテキストが切り替わる、とか書かれていると思います。

この「親スレッドに切り替わって処理される」というは、GUIプログラムでは重要で、それはUIの更新はメインスレッド（UIスレッド）でしか出来ない、という縛りがあるからです。理由は割愛しますが（長いので）、まぁそういうのがあるんです。

参考サイト：[async/awaitと同時実行制御](https://ufcpp.wordpress.com/2012/11/12/asyncawait%E3%81%A8%E5%90%8C%E6%99%82%E5%AE%9F%E8%A1%8C%E5%88%B6%E5%BE%A1/) (++C++; // 未確認飛行 C ブログ)
説明があっさりしていますが、信頼と実績の定番サイトです。

- デッドロックについて

さて、ポイント４は何が重要なのでしょうか？　試しに、`Task`を`await`で待ち受けするのではなく、`Result`でスレッドが終了するまで待ってみましょう。

```csharp:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click START");
		Task<int> task = Task.Run<int>(new Func<int>(Calculate));
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click Before");
		int result = task.Result; // ← await ⇒ Resultにしてみた
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click After");
		this.textBox1.Text = $"{result}";
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: button1_Click END");
	}

	private int Calculate()
	{
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate START");
		int total = 0;
		for (int i=0; i<=100; ++i)
			total += i;
		Thread.Sleep(4560);
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate END");
		return total;
	}
}
```

こうすると、メインスレッドでは`Result`で子スレッドの終了を待ち、子スレッドは処理が終了後メインスレッドに処理を復帰させようとするが、メインスレッドの処理が(`Result`で待ち続けているため)終わらないので、処理がストップしてしまいます。この状態をデッドロックと呼びます。絵で書くと次のような状態になっています。

![2017-11-28_161048.png](https://qiita-image-store.s3.amazonaws.com/0/208281/f0dd85f1-3287-ea07-d1a0-f59c010f13e6.png)

これが、よく他のサイトで書かれている `Task` を`Result`や`Wait()`で「待って」はいけない理由です。

# その他、説明できなかったこと

## スレッドに引数を渡す

ポイント１でスレッドを実行する方法を書きましたが、スレッドに引数を渡す方法は説明していませんでした。一番簡単なのは、`Run()`の引数をラムダ式でインライン化してしまえば、直接渡せます。

```csharp
private async void button1_ClickedAsync()
{
    int m = 999;
    Task<int> task = Task.Run(() => {
        Thread.Sleep(3000);
        return m * 2;
    });

    int result = await task; // -> 1998
    this.textBox1.Text = $"{result}";
}
```

ラムダ式の内部の値は、いつ評価されるのでしょうか。具体的には、次の場合、`task`の結果は `1998` でしょうか、`200` でしょうか。

```csharp
private async void button1_ClickedAsync()
{
    int m = 999;
    Task<int> task = Task.Run(() => {
        Thread.Sleep(3000);
        return m * 2;
    });
    m = 100;

    int result = await task; // -> いくつ？
    this.textBox1.Text = $"{result}";
}
```

C#の場合、`200` になります。ラムダ式の中で、変数が評価されるタイミングで、そのときの値を参照します。ちなみにこの動作は当たり前ではなく、（ラムダ式がある）言語によってマチマチです。C#は変数を評価するタイミングで、値を参照する、というルールになっているという話です。

それでは、ラムダ式を定義したときの値を使いたい、文字通りスレッドに引数を渡したい場合はどうすればいいでしょうか。それは`Task`コンストラクタの第２引数で値を渡せます。残念ながら、`Run()`メソッドには引数を渡すオーバーロードがありませんでした。`Task`を`new`した場合は、`Start()`でスレッドを開始できます。

```csharp
private async void button1_ClickedAsync()
{
    int m = 999;
    Task<int> task = new Task(x => {
        Thread.Sleep(3000);
        return x * 2;
    }, m);
    task.Start();

    m = 100;

    int result = await task; // -> 1998
    this.textBox1.Text = $"{result}";
}
```

## 非同期メソッドの戻り型(async void？、 async Task？)

非同期にしたいメソッドの戻り値が`void`であるとき、スレッドの実行結果をもらう必要がありません。この場合、`async Task HogeAsync()`ではなく`async void HogeAsync()`ではいけないのでしょうか？

`async void`は`Task`を返さないので、`async Task`と比べて次のことが出来なくなります。

- `await`で待ち受けできなくなり、そのためスレッドの終了を知ることが出来ない、投げっぱなし（fire and forget）になる。
- 例外を捕獲できない

`async void`にすること自体には問題ありません（例外を処理できないことを除いて）。 しかし、`async Task`で出来ることが`async void`で出来なくなり、その逆は無いため、`async void`を積極的に使う理由がありません。そのため、基本的には指向停止で`async Task`を使えば良いでしょう。`async void`を使いたいときは、もともと非同期にしたいメソッドが`void`でなければならない、つまりイベントハンドラ―ということになります。

参考サイト：[Async/Await - Best Practices in Asynchronous Programming](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) (英語です)

追記：ただしASP.NETの場合は、`async void`の場合は常に誤りになります。私はASP.NETは詳しくないので理由がよく分かりませんが、次のページを読んでくれたら、と思います。

参考サイト：[asyncの落とし穴Part3, async voidを避けるべき100億の理由](http://neue.cc/2013/10/10_429.html)　
ASP.NETで`aysnc void`を使っていはいけない話

## コンソールアプリケーションでの同期コンテキスト

ポイント４の話の続き。今までわざとらしくGUIアプリケーションにしていましたが、それはコンソールアプリケーションでは `await` 後で実行されるスレッドがGUIと異なっているからです。

```csharp:Program.cs
class Program
{
	static void Main(string[] args)
	{
		var task = CalculateAsync();
		var result = task.Result;

		Console.ReadLine();
	}

	static async Task<int> CalculateAsync()
	{
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: CalculateAsync START");
		var task = Task.Run(new Func<int>(Calculate));
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: CalculateAsync Before");
		var result = await task;
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: CalculateAsync After");
		return result;
	}

	static int Calculate()
	{
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate START");
		int total = 0;
		for (int i = 1; i <= 100; ++i)
			total += i;
		Thread.Sleep(2345);
		Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Calculate END");
		return total;
	}
}
```

> 9: CalculateAsync START
> 9: CalculateAsync Before
> 10: Calculate START
> 10: Calculate END
> <strong>10</strong>: CalculateAsync After

子スレッドのまま（ID=10）です。これは、コンソールアプリケーションの場合、（規定の）同期コンテキストがないため、戻れないからそのまま子スレッドで実行されます。これは、同じプログラムでも実行されるアプリケーションによって動作が異なることを意味しています。ライブラリ作成者はどうするんでしょうか。ASP.NET でも動作が異なるようなのですが、私はASP.NETは全く変わらないので、すみませんがこの記事では書きません（書けません）。

# まとめ

結構いろいろ端折ったのですが、それでもやはりスレッドの話は長くなってしまいました。`await` / `async`についてはこの位でいいのですが、スレッド全体としてはまだまだ不十分です。スレッドプールやスレッドローカル（変数）、スレッドの中断なんていうのもありますが、もう一つ(２つ)大きなテーマとして、スレッド間の同期を取る同期オブジェクトとシグナルの話があります。同期オブジェクトとシグナルの話は、また別の機会に書きたいと思います。

最後にポイントをまとめておきます。

- `Task.Run()`でスレッド実行。
- `Run()`の戻り型は `Task<T>`で、これはスレッドの終了結果をもらう予約（まだもらえてない）。
- `Task` は `await` で「待ち受け」する。待ち受けしている間は、メソッドを即リターンして別の処理を行っている。
- スレッドが終了すると、勝手に`await`の後ろから処理が再開する。そのとき実行するスレッドは親スレッドになる。
- スレッドが終了すると処理は親スレッドに移るので、`Task`を`Result`や`Wait()`で「待って」はいけない。
- <strike>非同期処理をするメソッドには `async` をつける。これは呼び出し側が `await` がいる、という目印になる。（厳密には、`async`が付いているメソッドは戻り型が`Task<T>`(C# 6.0以前)で、`Task<T>`は`await`で「待ち受け」するので`await`がいる） </strike>
- 追記：戻り値が `Task<T>` であれば、awaitを付けて「待ち受け」をする。安易に`Result`や`Wait()`で「待って」しまうと、デッドロックする場合がある。