UniRx&UniTask とは何なのか

# はじめに

何番煎じなのかわかりませんが、過去にいろいろ解説した記事へのリンクをまとめる意味も込めて解説します。

# UniRxとは

## おすすめ資料

* [UniRx入門シリーズ 目次](https://qiita.com/toRisouP/items/00b8a5bb8e7b68e0686c)
* [UniRx オペレータ一覧](https://qiita.com/toRisouP/items/0167ee5c41f79f55ad41)
* [UniRx オペレータ逆引き](https://qiita.com/toRisouP/items/3cf1c9be3c37e7609a2f)
* [ReactiveCommand/AsyncReactiveCommandについて](https://qiita.com/toRisouP/items/c6fba9f01e6d15dabd79)
* [ObserverパターンからはじめるUniRx](https://learning.unity3d.jp/1324/)
* [Observable の非同期処理への活用](https://learning.unity3d.jp/1516/)

## 概要

* [UniRx - GitHub](https://github.com/neuecc/UniRx)

UniRxとは、`Reactive Extensions`をUnity向けの実装したC#ライブラリです。
**かなり昔のバージョンのUnityでも扱うことができます。**
導入することで、Unityにおいて次のような処理の実装が簡単になります。

* **非同期処理**
    * 何らかの処理の完了を待ち受けて次の処理を行うような処理の管理
    * エラーハンドリングやリトライ処理の簡略化
    * 実行結果のハンドリングやキャッシュ
    * 実行スレッドの柔軟な切り替え

```cs
// data.txtをスレッドプール上で読み込み、メインスレッド上で表示する
Observable
    .Start(() => File.ReadAllText("data.txt"))
    .ObserveOnMainThread()
    .Subscribe(x => Debug.Log(x));
```

* **イベント処理**
    * 処理のトリガーと実際にハンドリングする場所を分離して記述できる
    * イベントメッセージの加工や合成
    * 実行スレッドの柔軟な切り替え

```cs
void Start()
{
    // Jumpボタンが押されたらログに出す
    // 1度発動したら1秒間は何もしない
    this.UpdateAsObservable()
        .Where(_ => Input.GetButtonDown("Jump"))
        .ThrottleFirst(TimeSpan.FromSeconds(1000))
        .Subscribe(_ => Debug.Log("Jump!!"));
}
```

UniRxは「非同期処理」と「イベント処理」の2つを扱うことができるライブラリです。
特に「時間」の扱いに長けており、Unityにおける「フレームをまたいだ処理」などの実装が簡単になります。

なお、`UniRx`が提供するこれら便利な機能は正式には「`Observable`」と呼ばれています。

## Observable

`Observable`とは`UniRx`が提供する、「メッセージを通知のための機構およびそのオブジェクト」の指します。
型としては`IObservable<T>`で表現されます。

```cs
// 実行結果は IObservable<T> でハンドリングする
private IObservable<string> ReadFileObservable(string path)
{
    return Observable
        .Start(() => File.ReadAllText(path))
        .ObserveOnMainThread();
}
```

`Observable`の仕組み、および`Scheduler`や`Operator`の概念について詳しく知りたい方は次の資料を御覧ください。

* [ObserverパターンからはじめるUniRx](https://learning.unity3d.jp/1324/)

## Operator一覧

`UniRx`でもっとも便利な機能がこの`Operator`です。
`Operator`は`Observable`に対するさまざまな処理を提供してくれます。
そのためこの`Operator`を組み合わせるだけで、だいたいの処理の実装が完了してしまいます。

* [UniRx オペレータ一覧](https://qiita.com/toRisouP/items/0167ee5c41f79f55ad41)
* [UniRx オペレータ逆引き](https://qiita.com/toRisouP/items/3cf1c9be3c37e7609a2f)

# UniTaskとは

## おすすめ資料

* [Deep Dive async/await in Unity with UniTask(UniRx.Async)](https://learning.unity3d.jp/1493/)
* [さては非同期だなオメー！async/await完全に理解しよう](https://learning.unity3d.jp/275/)
* [async/await のしくみ](https://learning.unity3d.jp/1510/)
* [実践! ライブコーディングで覚えるasync/await](https://learning.unity3d.jp/1513/)
* [UniTask入門](https://learning.unity3d.jp/2974/)
* [【Unity開発者向け】「SynchronizationContext」と「Taskのawait」](https://qiita.com/toRisouP/items/a2c1bb1b0c4f73366bc6)
* [UniTask機能紹介](https://qiita.com/toRisouP/items/4445b6b9bf00e49eb147)
* [UniRxとUniTask 相互変換の変わったパターン紹介](https://qiita.com/toRisouP/items/58f94a932972418f9c70)

## 概要

* [UniTask - GitHub](https://github.com/Cysharp/UniTask)

UniTaskとは、C#の標準TaskおよびTaskSchedulerをUnity向けに最適化して実装したC#ライブラリです。
UniTaskは標準Taskと比べてパフォーマンスが出るため、非同期処理を扱う場合は是非導入してほしいライブラリです。**ただし導入時はUnity 2018.3以降を推奨。**


UniTaskを導入することで、次のようなメリットがあります。

* **Unity向けに最適化されたTaskおよびTaskSchedulerが利用できる**
    * `ValueTask`のUnity向け実装
    * UnityのPlayerLoopを用いたSchedule管理
    * SynchronizationContextに依存しない

```cs
async void Start()
{
    var path = "data.txt";

    // UniTaskのawaitは基本的にメインスレッドに戻る
    var result = await LoadFileAsync(path);

    Debug.Log(result);
}

private UniTask<string> LoadFileAsync(string path)
{
    return UniTask.Run(() => File.ReadAllText(path));
}
```

* **さまざまなオブジェクトへの`Awaiter`実装の提供**
    * コルーチンを`async/await`に置き換える可能に
    * `yield return`の代わりに`await`で非同期処理の待機ができるようになる

```cs
private void Start()
{
    JumpAsync(this.GetCancellationTokenOnDestroy()).Forget();
}

/// <summary>
/// ボタンが押されたらジャンプする
/// </summary>
private async UniTaskVoid JumpAsync(CancellationToken token)
{
    var rigidBody = GetComponent<Rigidbody>();

    while (!token.IsCancellationRequested)
    {
        // ボタンが押されるのをUpdateタイミングで待つ
        await UniTask.WaitUntil(() => Input.GetButtonDown("Jump"),
            PlayerLoopTiming.Update,
            cancellationToken: token);

        // 押されたらFixedUpdateのタイミングでジャンプする
        await UniTask.Yield(PlayerLoopTiming.FixedUpdate);
        rigidBody.AddForce(Vector3.up, ForceMode.Impulse);

        // 1秒待って繰り返す
        await UniTask.Delay(1000, cancellationToken: token);
    }
}
```

UniTaskは「非同期処理」に特化したライブラリです。
導入することでほぼすべてのコルーチンを`async/await`に置換することができます。


## 標準Taskとの比較

`UniTask`は`C# 7.0`で追加された、`ValueTask`ライクに作られています。
そのため`async/await`時、処理が同期で完了する場合においてはヒープアロケーションが発生しないという特徴があります。また、`UniTask`は実行コンテキストの管理に`SynchronizationContext`ではなく`Unity Player Loop`を用いるように作られています。

そのため、標準の`Task`/`ValueTask`と比較して**UniTaskの方がよりUnityではパフォーマンスを出すことができるよう**になっています。


||Task|UniTask|
|:--:|:--:|:--:|
|機能|Unityでは不要な機能が多い|Unityで活用できる機能のみ実装|
|オブジェクトサイズ|大きい|小さい|
|実行コンテキストの管理|TaskScheduler & SynchronizationContext|UnityのPlayerLoop|
|必要なC# version|C# 5.0以上|C# 7.0以上|
|TaskTracker|無|UnityEditor上で確認可能|
|メモリアロケーション|常にヒープを確保する|同期処理で完了する場合はヒープを確保しない|



# UniRxとUniTask、それぞれの使い分け

「`UniTask`と`UniRx`の非同期処理の使い分けの基準は何か」という疑問を持つ人もいるでしょう。
それぞれの使い分けについて説明します。

## UniTaskを使うべき場合

#### 非同期処理の結果通知が「**1回**」で済む場合

結果通知が1回である、つまりは単発で完了する**普通の**非同期処理の場合です。
こちらは`UniTask`（と`async/await`）を使うべきです。

理由として次が挙げられます。

* 同期処理と見た目がほぼ変わらないコードを書ける
* 単発で済む非同期処理に対して、`Observable`自体がオーバースペック気味

`async/await`を使うと非同期処理をほぼ同期処理と変わらない見た目で記述することができます。
そのため同期処理で書いていた部分をあとから非同期化する、またはその逆といった対応も比較的楽にすみます。

`Observable`の場合、処理が1回で済む非同期処理に対してはかなりオーバースペックになってしまいます。まず「*メッセージの発行元は何か*」「*メッセージは何回発行されるのか*」を常に意識しなくてはいけません。
さらにはHotやColdといった性質も考えなくてはならず、`UniTask`ほど気軽に扱えません。

また、`Observable`を用いた非同期処理は同期処理とまったく異なる記述法になってしまいます。
そのため、あとから処理内容を同期処理へ直そうと思ったときに、`Observable`を使っている場所ほぼすべてを新しく書き直す必要が出てきてしまいます。

#### 処理を手続き的に記述したい場合

`Observable`では非同期処理を`Operator`を用いて処理を宣言的に記述することができました。
これは処理の内容が`Operator`の表現範囲で済む限りにおいては便利ではあります。
ですが実際の開発においてはそう簡単に行きません。`Operator`ではどうしても表現しきれない処理が出てきた場合、これを手続き的に書き下す必要があります。

また、`Observable`は「**処理の条件分岐ができない**」という欠点があります。
メッセージ内容に応じてその場で実行する処理をごっそり切り替えるといったことができません。
（処理内容ごとに新しい`Observable`を定義しなくてはいけないため無駄が多い）

`UniTask`（と`async/await`）であれば、処理をほぼ同期処理と変わらずに手続き的に記述することができます。
そのため、要求どおりの仕様を満たした処理を`Observable`と`Operator`で記述するよりも、よりわかりやすい簡単に実装することができます。


## UniRx（Observable）を使うべき場合

#### 非同期処理の結果が複数個になる場合

ここでいう複数個の結果とは、`UniTask<IEnumerable<T>>`で済むような場合の話ではありません。非同期処理そのものが継続的に実行され、結果が断続的に発行されるようなシチュエーションを想定しています。
（たとえばディレクトリの中身をまとめて読み込んで、読み込みが先に終わったファイルから処理を次々に行うなど）

このような非同期処理は`UniTask`では表現が不可能なため、`IObservable<T>`を使うことになります。

##### 補足 `IAsyncEnumerable<T>`と`IObservable<T>`の違い

`C# 8.0`で`IAsyncEnumerable<T>`というものが追加されました。名前のとおり「非同期ストリーム」です。`IObservable<T>`と役割が被ってそうですが、そこは明確に異なります。

* `IObservable<T>` : 非同期処理が常に動いておりその結果がPUSHで通知される
* `IAsyncEnumerable<T>` : 結果をPULLしたときに1つ非同期処理が走る

非同期処理を裏で動かしてその結果がPUSH通知されるのを待つなら`IObservable<T>`を使う。
`await foreach`と組み合わせて、逐次処理をしていくなら`IAsyncEnumerable<T>`を使う。

といった使い分けが今後は必要になってくるでしょう。
（まだUnityは`C# 8.0`に対応していないので、もう少しあとの話ですが）

#### イベント処理を行う場合

`C#`の`event`構文や、`UnityEvent`の代替として`UniRx`は利用することができます。
また`Update()`や`FixedUpdate()`を`Observable`に変換したり、`uGUI`と組み合わせて使うと表現力が広がりかなり便利に使うことができます。

（そもそも「イベント処理」=「値が複数回発行される非同期処理」とほぼ同義なので、同じことを2回説明しているだけですが）

#### Unityで動くC#バージョンが低い場合

古のUnityでは対応しているC#バージョンが低く、`async/await`すら使えない場合があります。
特に長期運用しているプロジェクトですと未だにUnity 5系を使っているなんてこともあるでしょう。

この場合は`UniTask`を用いることはできないので、**UniRxが非同期処理における唯一の選択肢**となります。
コルーチンと`Observable`を併用するなどするとよいでしょう。

* [UniRx入門 その5 -コルーチンとの併用-](https://qiita.com/toRisouP/items/c4b9c5701dd6c991b481)


## 相互変換

なお、`Observable`と`UniTask`はそれぞれ相互変換が可能です。
そのためどちらか片方にこだわる必要はなく、場面に応じて変換して使い分けるとよいでしょう。

### UniTask -> UniRx

#### キャンセルを考えない場合

`UniTask.ToObservable()`で変換できます。
この場合、`Scheduler`は自動的に`MainThreadScheduler`指定となります。

```cs
using UniRx;
using UniRx.Async;
using UnityEngine;
using UnityEngine.Networking;

public class UniTaskToUniRx : MonoBehaviour
{
    void Start()
    {
        // UniTask<string>
        var uniTask = LoadTextAsync("https://github.com/");

        // UniTask<string> -> IObservable<string>
        // UniTaskのキャンセルを考えないならこれだけでOK
        uniTask.ToObservable().Subscribe(Debug.Log);
    }

    /// <summary>
    /// Textを指定のパスから読み込む
    /// </summary>
    private async UniTask<string> LoadTextAsync(string path)
    {
        var uwr = UnityWebRequest.Get(path);
        await uwr.SendWebRequest();
        return uwr.downloadHandler.text;
    }
}
```

#### キャンセルを考える場合

一発で変換するメソッドはありません。
そのため次のような方法でがんばるしかないです。

* [UniTaskをCancellationTokenを指定しながらToObservableするメモ](https://qiita.com/toRisouP/items/8ec18d73d9e8c5169587)

### UniRx -> UniTask

#### IObservable<T> -> UniTask<T>

`ToUniTask()`を使うことで`UniTask`に変換できます。
ただしデフォルトでは`OnCompleted`メッセージが発行されるまで待ち受けてしまいます。
次に発行される1メッセージだけを待ち受けたい場合は、`useFirstValue`オプションをつけましょう。

```cs
private async UniTaskVoid CheckEnemyAsync(CancellationToken cancellationToken)
{
    // 新たに生成されたEnemyが通知されるObservableがあったとして
    IObservable<Enemy> enemyObservable = _enemySpawner.OnEnemySpawned;

    while (!cancellationToken.IsCancellationRequested)
    {
        // ToUniTask(useFirstValue: true) でUniTask化してawaitできる
        var enemy = await enemyObservable
            .ToUniTask(cancellationToken, useFirstValue: true);

        Debug.Log(enemy.Name);
    }
}
```

#### IReadonlyReactiveProperty<T>のawait

正確にいえば`UniTask`変換ではないですが、どうせ`async/await`とセットで使うので解説します。
`IReadonlyReactiveProperty<T>`は`await`することで、次のメッセージ発行を待ち受けることができます。

```cs
using System.Threading;
using UniRx;
using UniRx.Async;
using UniRx.Async.Triggers;
using UnityEngine;

public class ReactivePropertyAwait : MonoBehaviour
{
    // なにかのステート
    private enum GameState
    {
        Ready,
        Battle,
        Result
    }

    // ステート管理するReactiveProperty
    private ReactiveProperty<GameState> _currentGameState
        = new ReactiveProperty<GameState>();

    private void Start()
    {
        StateChangedAsync(this.GetCancellationTokenOnDestroy()).Forget();
    }

    /// <summary>
    /// ステート遷移するたびに処理を走らせる
    /// </summary>
    private async UniTaskVoid StateChangedAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // ステート遷移を待つ
            var next = await _currentGameState;

            // 遷移先に合わせて処理をする
            switch (next)
            {
                case GameState.Ready:
                    // Do something...
                    break;
                case GameState.Battle:
                    // Do something...
                    break;
                case GameState.Result:
                    // Do something...
                    break;
            }
        }
    }
}
```


### その他、変わった変換の例

その他の変わった変換パターンについては別記事にまとめました。

* [UniRxとUniTask 相互変換の変わったパターン紹介](https://qiita.com/toRisouP/items/58f94a932972418f9c70)


### まとめ

`UniRx`と`UniTask`はUnityで`C#`を触るならぜひとも導入してほしいライブラリです。
入れて絶対に損はしない、むしろ無いと困るくらいには便利なものなので使い方を覚えて活用できるようになるとよいでしょう。

また、現在UniRxとUniTaskの書籍の執筆を行っています。全体の執筆が概ね終わり、現在はレビューフェーズに入っています。発売されたらぜひよろしくおねがいします。