非同期理解のためにasync/awaitとTaskの基礎を学んだ話

# 最初に

どうも、[ろっさむ](https://twitter.com/Ev_rossam)です。

引き続きUnity非同期を完全に理解するために今回は`async/await`について見ていこうと思います。
ちなみにこれが2018年最後の記事となります。お疲れ様でした。

Unity非同期完全に理解するための歩み：
- 【Part0】[タスクとスレッドとプロセスの違いを知って非同期完全理解に近付く](https://qiita.com/4_mio_11/items/3b83c11bb0fac4844604)
- 【Part1】[Unity非同期完全に理解するための第一歩～非同期処理とは何か～](https://qiita.com/4_mio_11/items/3a0b06819d9591c5d410)
- 【Part2】[古来よりUnity非同期を実現していたコルーチンとは何者か？](https://qiita.com/4_mio_11/items/3030f9a39f59cd68f705)
- 【Part3】非同期理解のためにasync/awaitとTaskの基礎を学んだ話 ←今ココ！

# async/await？

**C#5.0以降から追加された機能**です。Unity2018以降ではC#6.0が使用できるため、この`async/await`も使用することが可能です。

`async/await`は**Taskクラスと紐付いた言語構文の一つ**です。

多くの場面でシングルスレッド的な動作になり、lock不要となったりします。

`await`とは「待つ」と言う意味で、`await`をつけた処理は**一旦別スレッドに制御を移した上で、処理完了後に続きの処理を再開**します。**「式」が書ける場所であればどこにでも書くことができます**。C#6.0以降は`catch/finally`の中でも`await`演算子を使用することができます。

`async`は`await`をメソッド内で使用する際に、**メソッドにつける必要があるキーワードです（async:Asynchronous:非同期）**。ただしメソッドに`async`をつけただけでは**通常のメソッドと挙動は変わりません**。実際に意味を持つのは`await`の方となります。

`async/await`は**「処理を非同期的に行う仕組み」ではなく「非同期の処理を待つための仕組み」**となります。

構文としては以下のようになります。

```
async 戻り値の型 関数名( 引数 )
{
    同期的な処理　または　非同期メソッドをawaitで待機するコード
}
```

asyncをつけたメソッドの戻り値は

①**非同期の完了を待つ必要がない**場合に`void`
②**完了を待つ必要がある**場合には**非同期メソッドビルダー(`Task、Task<T>、ValueTask<T>`など)を実装した型**である必要がある（`ValueTask<T>`はC#7.0から）

となります。ただvoidはメリットが薄いためそこまで使われていない印象です。

# 非同期メソッドビルダーの実装型

Taskクラスは便利な非同期メソッドが数多く用意されています。

## `Task`

非同期メソッドの完了を待つ場合、**返り値がない場合**は`Task`を用います。

```C#
async Task WaitMethod()
{
    await Task.Delay(3000);
    // returnステートメント不要
}
```

### Task.Run()

**引数として渡したデリゲートを別スレッドで実行するタスク**を作成することができます。重たい処理をUIスレッドではなく別スレッドで行いたい場合に使用します。

```
Task.Run(デリゲート(ラムダ式など))
```

```C#
var x = 500;
// 重い処理なのでUIスレッドから逃す
await Task.Run(() => 
{
    HeavyWork(x)
});

// ラムダ式を与えるコード
await Task.Run(() => {
    Console.WriteLine("処理1");
    Thread.Sleep(3000);
    Console.WriteLine("処理2");
    Thread.Sleep(3000);
    });
```

### Task.WhenAll()

**全てのタスクが完了したら完了とするタスク**を作成することができます。

```C#
var x = 500;
await Task.WhenAll(
    HeavyWork1(x),
    HeavyWork2(x),
    HeavyWork3(x));
```

## `Task<T>`

**戻り値が欲しい場合**は`Task<T>`を使います。

```C#
private async Task<string> Hoge()
{
    await Task.Delay( 3000 );

    return "ほにゃー";
}
```

```C#
string result = await Hoge();
Console.WriteLine($"{result}");
```

## `ValueTask`

C#7.0以降から使用できます。どんな場合に使用するかと言うと、まず以下のコードを確認してください。

```C#
async Task<string> Hoge(int num)
{
    if(num == 100)
    {
        await Task.Delay(3000);
        return "OK";
    }

    return "NG";
}
```

上記コードですと、`num`の値が100の場合にのみ非同期処理が行われます。この場合に、ほんの一部分しか非同期処理が行われないのにTask(参照型)のインスタンスを生成するとコストが大きくなってしまいます。ここでValueTask構造体(値型)を使うことで**非同期が必要な場合にのみ内部でTaskのインスタンスが作られる**ようになり、同期的処理がメインのメソッドのパフォーマンスが大幅に向上します。

なぜ、TaskとValueTaskで使い分けを行うかと言うと、`WhenAll`がTaskしか受け取らないことが起因しています。`Task.WhenAll`はneue.ccさんのブログに寄ると、

> Taskのinternalなメソッドに依存して最適化が施されているので、外部からはどうしても非効率的なWhenAllしか作れない仕様になっています（クソですね！）。

とのことです。

悲しいですね。

# 非同期ラムダ式

asyncを付けたラムダ式を非同期ラムダ式と呼びます。

```C#
myButton.Click += async ( sender, e ) => 
{
        // なんか処理
        await Task.Delay( 3000 );
        myTextBox.Text = "OK";
};
```

## 注意点

`async/await`は実は**必ずしも非同期実行にはなりません**。Taskクラスの値をawaitする際に、タスクが既に完了している可能性があるからです。

またTask自体もスレッドの存在を意識しない、「同期的な処理のまとまり」とだけみなされているため、どのスレッドが対象のタスクを実行するかは定められていません。TaskクラスでのRun()等は**可能な限り同じスレッドを使いまわそうとします**(スレッドプール上で動作)。

# その他

## Awaitableパターン

`Awaitable`パターンを満たしていれば、**Task型以外の型を`await`できる**ようになります。`await`したいクラスに`GetAwaiter`拡張メソッドを実装すれば良いようです(詳細は省略)。

## 例外

`async/await`がキャンセルされた場合には、awaitされた元に`OperationCanceledException`と言う特別な例外が投げられます。

# 最後に

本記事を書くことで`async/await`を少し理解できたような気がします。ただ、実際の使い心地的なところやパフォーマンス面ではUniRxを使うのが主流となっているようです。今回はUniRxについては触れませんでしたが、また色々調べて使ってみた記事などあげ、非同期完全理解に近づけたらなと思います。それでは有難うございました。

# 参考
- [【Unite Tokyo 2018】さては非同期だなオメー！async/await完全に理解しよう](https://www.slideshare.net/UnityTechnologiesJapan/unite-tokyo-2018asyncawait)
- [async/await のしくみ](https://www.slideshare.net/ufcpp/asyncawait-114647813?ref=https://connpass.com/event/95696/presentation/)
- [UniTask(UniRx.Async)から見るasync/awaitの未来](http://neue.cc/2018/12/25_571.html)