UniTask Ver2 、UniTaskAsyncEnumerableまとめ

# 他の資料

[C# Tokyo オンライン「Unity 祭り」](https://csharp-tokyo.connpass.com/event/175469/)で登壇した資料も追加しておきます。あわせて御覧ください。

* [UniTaskの使い方2020（スライド）](https://speakerdeck.com/torisoup/unitask2020)
* [発表動画（YouTube）](https://youtu.be/IcSl_7UENQ8)


# UniTask2

`UniTask`が大きくアップデートされて、Ver2となりました！
今回はその変更点についてまとめてみました。

※ `Ver.2.0.25`準拠で書いています。

# 破壊的変更

まずは1系からの破壊的変更。導入時に気をつける必要があるところです。

## 必要な最低Unityバージョン

**2018.4.13f1** が最低サポートバージョンとなりました。

## 名前空間の変更

名前空間が`UniRx.Async`から`Cysharp.Threading.Tasks`に変更されました。
もともと`UniRx`のライブラリの一部だった名残なのですが、今回のアップデートで完全に名残がなくなりました。

## UniTaskのawait2度漬け禁止

同じ`UniTask`に対しての2回以上の`await`が明確に禁止となりました。
`IValueTaskSource`準拠の挙動なので、これは本家`ValueTask`と同じ挙動です。

```cs:UniTaskの2回以上のawait禁止
private async UniTaskVoid DoAsync(CancellationToken token)
{
    try
    {
        var uniTask = GetAsync("https://unity.com/ja", token);

        // 1回目のawaitは問題ない
        await uniTask;

        // 同じオブジェクトに対して2回以上のawaitはできない
        // (InvalidOperationExceptionが発行される)
        await uniTask;
    }
    catch (InvalidOperationException e)
    {
        Debug.LogException(e);
    }
}

private async UniTask<string> GetAsync(string uri, CancellationToken token)
{
    var uwr = UnityWebRequest.Get(uri);
    await uwr.SendWebRequest().WithCancellation(token);
    return uwr.downloadHandler.text;
}
```

### Preserve()

もし同じ`UniTask`を2回以上`await`する必要があるならば、`Preserve()`を利用しましょう。

```cs:Preserve()
private async UniTaskVoid DoAsync(CancellationToken token)
{
    try
    {
        var uniTask = GetAsync("https://unity.com/ja", token);

        // Preserve()で何回でもawait可能なUniTaskに変換
        var reusable = uniTask.Preserve();

        await reusable;
        await reusable;
    }
    catch (InvalidOperationException e)
    {
        Debug.LogException(e);
    }
}
```



## `AsyncOperation`を`await`するときの`ConfigureAwait`廃止

`AsyncOperation`を`await`するときに利用できていた`ConfigureAwait`が廃止となりました。

`IProgress<float>`や`CancellationToken`を指定したい場合は`ToUniTask()`または`WithCancellation()`を利用する必要があります。

```cs
private async UniTask<string> GetAsync(string uri, CancellationToken token)
{
    using (var uwr = UnityWebRequest.Get(uri))
    {
        // CancellationTokenを指定
        await uwr.SendWebRequest().WithCancellation(token);
        return uwr.downloadHandler.text;
    }
}
```

```cs

// IProgress<float>を指定したい場合（あとついでにCancellationToken）
var urw2 = UnityWebRequest.Get("https://unity.com/ja");
await urw2.SendWebRequest()
    .ToUniTask(
        Progress.Create<float>(x => Debug.Log(x)),
        cancellationToken: token);
```

## UniTaskの一部コンストラクタ廃止

`UniTask`の一部コンストラクタが廃止されました。
こちらは`UniTask`の起動を遅延実行するために利用できたコンストラクタです。
（`UniTask.Lazy`に相当していた）

```cs:廃止されたコンストラクタ
public UniTask(Func<UniTask<T>> factory)
```

これ相当のファクトリメソッドとして、`UniTask.Defer`と`UniTask.Lazy`があるのでこちらを使いましょう。


## UniTask.Lazyの戻り値の変更

上記の多重`await`禁止の変更をうけて、`UniTask.Lazy`の戻り値が`AsyncLazy`に変換されました。

```cs
public static AsyncLazy Lazy(Func<UniTask> factory)
public static AsyncLazy<T> Lazy<T>(Func<UniTask<T>> factory)
```

`AsyncLazy`は`awaitable`なので、そのまま`await`することができます。
もし`UniTask`に変換したい場合は`AsyncLazy.Task`を使いましょう。

```cs
var asyncLazy = UniTask.Lazy(Factory);
await asyncLazy; // 直接待てる
await asyncLazy.Task; // UniTaskに変換

// UniTask.Lazyの結果は何回awaitしてもOK
```


## UniTask.WhenAnyの戻り値変更

`WhenAny`の戻り値も変更されました。

```cs:旧
public static async UniTask<(int winArgumentIndex, (bool hasResult, T0 result0), (bool hasResult, T1 result1), (bool asResult, T2 result2))> 
WhenAny<T0, T1, T2>()
```

```cs:新
public static UniTask<(int winArgumentIndex, T1 result1, T2 result2, T3 result3)> WhenAny<T1, T2, T3>()
```

## UniTask.DelayFrameの戻り値変更

`UniTask.DelayFrame`の戻り値が`UniTask<int>`から`UniTask`に変更されました。

## IObservable<T>.ToUniTask()の引数の順序が変更

`CancellationToken, bool useFirstValue` の順序が `bool useFirstValue, CancellationToken`と逆になりました。

```cs:旧
public static UniTask<T> ToUniTask<T>(this IObservable<T> source, CancellationToken cancellationToken = default(CancellationToken), bool useFirstValue = false)
```

```cs:新
public static UniTask<T> ToUniTask<T>(this IObservable<T> source, bool useFirstValue = false, CancellationToken cancellationToken = default)
```

## UniTask.Result/IsCompletedが削除

`UniTask`から同期的に結果を取得する`Result`プロパティが削除されました。
あと完了したかを表す`IsCompleted`も削除されました。

もし同等の機能が必要な場合は`GetAwaiter()`を経由することで一応取得はできます。

```cs
// uniTask.Result相当
var result = uniTask.GetAwaiter().GetResult();

// IsCompleted相当
var isCompleted = uniTask.GetAwaiter().IsCompleted;
```

## AwaiterStatus -> UniTaskStatus

`UniTask`の状態を表す`enum`の名前が`AwaiterStatus`から`UniTaskStatus`に変わりました。


# 既存機能への追加機能とか

## 全体的なパフォーマンスの向上

全体的にパフォーマンスが向上しています。
内部で`UniTask`と`AsyncMethodBuilder`、`Runner`の再利用が自動的に行われるようになり、ゼロアロケーションで動作するようになりました。

## UniTaskCompletionSource

~~`UniTaskCompletionSource`に`Reset()`メソッドが追加されました。
こちらを利用することで`UniTaskCompletionSource`を再利用することができるようになりました。~~


`UniTaskCompletionSource.Reset()`は`2.0.19`で廃止されました。
かわりに`UniTaskCompletionSource`の挙動が`TaskCompletionSource`に揃えられました。

すなわち**UniTaskCompletionSourceから生成したUniTaskは何回でもawaitができる**という仕様に変更されました。
（`UniTaskCompletionSource`をフィールドに定義して、そこから生成した`UniTask`をプロパティとして公開しても問題なくなった）



## AutoResetUniTaskCompletionSource追加

`AutoResetUniTaskCompletionSource`も追加されました。
こちらは`UniTaskCompletionSource`と破棄したときに内部のインスタンスが自動的に再利用される仕組みになっています。

なお、こちらから生成した`UniTask`は1回しか`await`できません。
メソッド内で`UniTask`を生成する、みたいな局所的な場所で`UniTaskCompletionSource`を生成してすぐ使い捨てるみたいな場合はこちらを使ったほうがよいでしょう。

**追記**

`AutoResetUniTaskCompletionSource` で生成したインスタンスについては、2回`TrySet~`メソッドを呼び出すと状態が壊れ、次に生成したインスタンスの挙動がおかしくなってしまいます。

参考: [UniTask2のAutoResetUniTaskCompletionSourceでは2回TrySetしてはいけない](https://qiita.com/sakano/items/198b178515332c91332e)

## ファクトリメソッドが追加

ファクトリメソッドがいくつか追加されました。

* UniTask.Create
* UniTask.Defer
* UniTask.Action
* UniTask.UnityAction
* UniTask.WaitUntilCanceled
* UniTask.NextFrame
* UniTask.WaitForEndOfFrame
* UniTask.WaitForFixedUpdate


### UniTask.Create, UniTask.Defer, UniTask.Delay

`UniTask`をデリゲートから生成するファクトリメソッドとして、次の3つがあります。

* UniTask.Create
* UniTask.Defer
* UniTask.Lazy

```cs
// 引数が同じで挙動も似てる3つのファクトリメソッド
public static UniTask<T> Create<T>(Func<UniTask<T>> factory)
public static UniTask<T> Defer<T>(Func<UniTask<T>> factory)
public static AsyncLazy<T> Lazy<T>(Func<UniTask<T>> factory)
```

これら3つは引数がどれも同じですが、微妙に挙動に違いがあります。

|名前|効果|備考|
|:--|:--|:--|
|UniTask.Create|メソッドが呼び出されたタイミングで新しい`UniTask`を**即座に**生成する|`Create()`を呼んだ瞬間に`UniTask`が起動する。|
|UniTask.Defer|`await`されるタイミングまで`UniTask`の生成を遅延させる|1回しか`await`できない代わりに`Lazy`より軽量。|
|UniTask.Lazy|最初に`await`されるタイミングまで`UniTask`の生成を遅延させる|生成されるものは`AsyncLazy`。`AsyncLazy.Task`は何回でも`await`できる。`Defer`よりはコストが大きい。|

`Defer`と`Lazy`の違いは直接`UniTask`を生成するか、`AsyncLazy`を生成するかの違いです。

`Defer`から生成される`UniTask`は2回`await`できません。
`Lazy`から生成される`UniTask`(`AsyncLazy.UniTask`)は何回でも`await`が可能です。

`Lazy`と比べて`Defer`の方がより軽量になっています。
そのため`await`回数が1度のみなら`Defer`を使いましょう。

一方で何度も`await`する必要がある場合は`Lazy`を使いましょう。

## PlayerLoopTiming追加

`PlayerLoopTiming`が追加され、次の種類となりました。

* Initialization
* LastInitialization
* EarlyUpdate 
* LastEarlyUpdate
* FixedUpdate
* LastFixedUpdate
* PreUpdate
* LastPreUpdate
* Update
* LastUpdate
* PreLateUpdate
* LastPreLateUpdate
* PostLateUpdate
* LastPostLateUpdate

とくに**LastPostLateUpdate**が重要で、こちらはコルーチンにおける`WaitForEndOfFrame`に相当します。
いままで`UniTask`では`yield return new WaitForEndOfFrame()`ができなかったのですが、今回のアップデートから可能となりました。

## JobHandle.WaitAsync

`JobSystem`の`JobHandle`に`WaitAsync`が追加されました。
こちらを利用することで、任意の`PlayerLoop`のタイミングに切り替えてから完了待ちができます。

```cs
// WaitForEndOfFrame相当の待機をしてからComplete()
await jobHandle.WaitAsync(PlayerLoopTiming.LastPostLateUpdate);
```

## AsyncTriggerメソッドのIUniTaskAsyncEnumerable対応

`AsyncTrigger`系のメソッド(`this.GetAsyncCollisionEnterTrigger`とか)が`IUniTaskAsyncEnumerable`に対応しました。


## `CancellationToken.WaitUntilCanceled`

`CancellationToken.WaitUntilCanceled`という拡張メソッドが追加されました。
これを用いると、`CancellationToken`が完了するのを`await`できるようになります。


```cs
private void Start()
{
    var token = this.GetCancellationTokenOnDestroy();
    WaitForCanceledAsync(token).Forget();
}

private async UniTaskVoid WaitForCanceledAsync(CancellationToken token)
{
    await token.WaitUntilCanceled();
    Debug.Log("Canceled!");
}
```

## `CancellationToken.ToUniTask`

同様に、`CancellationToken.ToUniTask`で`UniTask`を生成することもできます。
この`UniTask`は`CancellationToken`がキャンセルされると正常終了（`Succeeded`）状態となります。



## DoTweenのawaitに対応

`DoTween`の`await`にも対応しました。

ただし動作させるためには次の設定が必要です。

* OpenUPMからDOTweenを導入する、またはScripting Define Symbolsに「UNITASK_DOTWEEN_SUPPORT」を定義する
* asmdef「`UniTask.DOTweeen`」への参照を追加する

```cs
private void Start()
{
    MoveAsync().Forget();
}

private async UniTaskVoid MoveAsync()
{
    // 直列実行
    await transform.DOMove(transform.position + Vector3.up, 1.0f);
    await transform.DOScale(Vector3.one * 2.0f, 1.0f);

    // UniTask.WhenAllで並行実行して終了待機
    await
    (
        transform.DOMove(Vector3.zero, 1.0f).ToUniTask(),
        transform.DOScale(Vector3.one, 1.0f).ToUniTask()
    );
}
```

## Addressable Asset Systemのawaitに対応

`AsyncOperationHandle`の`await`も可能になりました。

```cs
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;

public class AsyncOperationHandleAwaitSample : MonoBehaviour
{
    /// <summary>
    /// 読み込む対象のAssetReference
    /// </summary>
    [SerializeField] AssetReference _target;

    [SerializeField] private RawImage _image;

    private void Start()
    {
        var token = this.GetCancellationTokenOnDestroy();
        InitializeAsync(_target, token).Forget();
    }

    private async UniTaskVoid InitializeAsync(
        AssetReference target,
        CancellationToken token)
    {
        // Addressables.LoadAssetAsyncをawaitで待ち受ける
        var texture = await Addressables.LoadAssetAsync<Texture>(target)
            .WithCancellation(token);

        _image.texture = texture;
    }
}
```

## IDisposable.AddTo(CancellationToken)

`UniRx`で便利だった`AddTo`の`CancellationToken`版も追加されました。


# 新機能

## UniTaskAsyncEnumerable/IUniTaskAsyncEnumerable<T>

`UniTaskAsyncEnumerable`は`UniTask2`の目玉機能です。

`IUniTaskAsyncEnumerable<T>`は`C# 8.0`の`IAsyncEnumerable<T>`を`UniTask`として実装したものです。
なんとこちらは**C# 7.x系のUnityでも利用可能**になっています。

何をするためのものかというと、「**非同期処理を複数個まとめて扱う**」ことができるようになる機能です。
`Observable`（`IObservable<T>`）と似ていますが、こちらは`Pull`型として機能するという違いがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/db67ecfe-98e5-5eaf-c0f8-8cb47ee102ef.png)


### Observableとの使い分け

`Observable`は`Push`型なのに対して、`UniTaskAsyncEnumerable`は`Pull`型です。
そのため`UniTaskAsyncEnumerable`では非同期処理の実行タイミングを受信側でコントロールできるというメリットがあります。

```cs:UniTaskAsyncEnumerableで複数の非同期処理を扱う
private async void Start()
{
    var token = this.GetCancellationTokenOnDestroy();

    var uris = new[]
    {
        "https://www.google.com/",
        "https://unity.com/ja",
        "https://github.com/"
    };


    // URIのリストに対してアクセスしてデータをとってくる
    // ただし常に実行される通信は同時に1つであり、
    // 前回のものが完了しないと次の通信に進まない
    await uris.ToUniTaskAsyncEnumerable()
        // 通信完了を待機してメッセージを発行する
        .SelectAwait(async x => await FetchAsync(x))
        .ForEachAsync(x => Debug.Log(x), token);
}


private async UniTask<string> FetchAsync(string uri)
{
    using (var uwr = UnityWebRequest.Get(uri))
    {
        await uwr.SendWebRequest();
        if (uwr.isNetworkError || uwr.isHttpError)
        {
            throw new Exception($"Error>{uwr.error}");
        }

        return uwr.downloadHandler.text;
    }
}
```

対する`Observable`は`Push`型のため、多数の`Observer`に対してメッセージをブロードキャストするのに向いています。

複数個の非同期処理を管理する場合は基本には`UniTaskAsyncEnumerable`を使う。
イベント駆動を制御する場合には`Observable`を使う。

という使い方をするとよいでしょう。

### UniTaskAsyncEnumerableの消費

#### ForEachAsync

`C# 8.0`であれば`await foreach`が使えるのですが、それが使えないUnityバージョンでは代替として`ForEachAsync`を利用します。
感覚としては、`IObservable<T>`に対する`Subscribe()`に似ています。
ですがこちらは「`ForEachAsync()`の完了をさらに`await`で待つ」といったことが可能となります。

```cs:ForEachAsync
private async void Start()
{
    var token = this.GetCancellationTokenOnDestroy();

    // EveryUpdate()は毎フレームのタイミングで完了するUniTaskを返す
    await UniTaskAsyncEnumerable.EveryUpdate()
        .Select((_, x) => x)
        // 5回まで実行する
        .Take(5)
        // ForEachAsyncで待機する
        .ForEachAsync(_ => Debug.Log(Time.frameCount), token);

    Debug.Log("Done!");
}
```

#### ForEachAwaitAsync

また、`ForEachAsync`の他に`ForEachAwaitAsync`もあります。
こちらはデリゲートの内部で`async/await`が利用でき、この非同期処理が完了するまで次のメッセージを取りに行きません。

```cs:ForEachAwaitAsync
await UniTaskAsyncEnumerable.EveryUpdate()
    .Select((_, x) => x)
    // 5回まで実行する
    .Take(5)
    // ForEachAwaitAsyncで待機する
    .ForEachAwaitAsync(async _ =>
    {
        // 10フレーム待ってから次のメッセージを取りに行く
        await UniTask.DelayFrame(10, cancellationToken: token);
    }, token);
```

#### Subscribe

また、`Subscribe`というメソッドも用意されています。
こちらは`IObservable.Subscribe()`と似た感覚で利用できます。

なお、こちらはデリゲート内部で`async/await`が利用できますが、**その結果を待機しません(Forgetする）**。

```cs
var token = this.GetCancellationTokenOnDestroy();
UniTaskAsyncEnumerable.EveryUpdate()
    .Select((_, x) => x)
    .Take(5)
    .Subscribe(async (_, ct) =>
    {
        await UniTask.Delay(100, cancellationToken: ct);
        Debug.Log("Do!");
    }, token);
```


#### ForEachAsync/ForEachAwaitAsync/Subscribeの違い

それぞれの大きな違いは登録したデリゲートの呼び出し方にあります。
`ForEachAwaitAsync`と`Subscribe`は非同期処理を用いたときに挙動が異なるので注意が必要です。

```cs:ForEachAsync
while (await e.MoveNextAsync())
{
    action(e.Current); // 常に同期
}
```

```cs:ForEachAwaitAsync
while (await e.MoveNextAsync())
{
    await action(e.Current); // awaitしてから次へいく
}
```

```cs:Subscribe
while (await e.MoveNextAsync())
{
    action(e.Current).Forget(); // Forget()して次へいく
}
```


### UniTaskAsyncEnumerableの作り方

`UniTaskAsyncEnumerable`はさまざまな方法で生成することができます。

#### ファクトリメソッド

* Return
* Repeat
* Empty
* Throw
* Never
* Range
* EveryUpdate
* Timer
* TimerFrame
* Interval
* IntervalFrame
* EveryValueChanged

##### UniTaskAsyncEnumerable.Create

C#8の非同期イテレータに相当するものとして、`UniTaskAsyncEnumerable.Create`があります。

```cs
// 整数のカウントダウン
private IUniTaskAsyncEnumerable<int> CountDownAsync(int startCount, TimeSpan timeSpan)
{
    return UniTaskAsyncEnumerable.Create<int>(async (writer, token) =>
    {
        var currentCount = startCount;
        while (currentCount >= 0)
        {
            // writer.YieldAsync を使うと UniTaskAsyncEnumerable に値が書き込まれる
            await writer.YieldAsync(currentCount--);
            await UniTask.Delay(timeSpan, cancellationToken: token);
        }
    });
}
```

#### 他のデータ構造から変換

* `IEnumerable<T>`
* `IObservable<T>`
* `Task<T>`
* `UniTaskT>`

#### uGUIコンポーネントから変換

```cs:uGUIイベントからの変換
public class FromUGui : MonoBehaviour
{
    [SerializeField] private Button _button;

    private void Start()
    {
        var token = this.GetCancellationTokenOnDestroy();

        // 連打防止ボタン
        // 1回ボタンを押したら2秒間無反応になる
        _button.OnClickAsAsyncEnumerable()
            .ForEachAwaitWithCancellationAsync(async (_, ct) =>
            {
                Debug.Log("Clicked!");
                await UniTask.Delay(2000, cancellationToken: ct);
            }, token);
    }
}
```

#### AsyncTriggerを利用する

`this.GetAsyncCollisionEnterTrigger()`など。




#### Channel

`Channel`は`Go`における`Channel`と同義です。
挙動としては`Observable`における`Subject`に相当します。

実装としては`CreateSingleConsumerUnbounded`が用意されています。
`CreateSingleConsumerUnbounded`は内部でメッセージをキューイングしているため、メッセージの取りこぼしは発生しません。


```cs:Channelの例
 private void Start()
{
    // Channel作成
    var channel = Channel.CreateSingleConsumerUnbounded<int>();

    // Channelを読み取るときはReaderを使う
    var reader = channel.Reader;

    // メッセージの待受
    WaitForChannelAsync(reader, this.GetCancellationTokenOnDestroy()).Forget();

    // 書き込むときはWriteを使う
    var writer = channel.Writer;

    // IObserver<T>.OnNext() に相当
    writer.TryWrite(1);
    writer.TryWrite(2);
    writer.TryWrite(3);

    // IObserver<T>.OnCompleted() に相当
    writer.TryComplete();

    // TryComplete()に例外を渡すと IObserver<T>.OnError() に相当
    // writer.TryComplete(new Exception(""));
}

private async UniTaskVoid WaitForChannelAsync(ChannelReader<int> reader, CancellationToken token)
{
    try
    {
        // 1回だけ読み取るならReadAsync
        var result1 = await reader.ReadAsync(token); // UniTask<int>
        Debug.Log(result1);

        // 完了するまで読み続けるなら　ReadAllAsync
        // IObservable<T>.Subscribe() に相当
        await reader.ReadAllAsync() // IUniTaskAsyncEnumerable<int>
            .ForEachAsync(x => Debug.Log(x), token);

        Debug.Log("Done");
    }
    catch (Exception e)
    {
        Debug.LogException(e);
    }
}
```


なお、`CreateSingleConsumerUnbounded`は同時に１箇所でしかメッセージの消費が行なえません。
2箇所以上で`ForEachAsync`等を行った場合は正しく動作しません。

もし2箇所以上で消費したい（値を分配したい）場合は`Publish()`を利用しましょう。

```cs
private void Start()
{
    // Channel作成
    var channel = Channel.CreateSingleConsumerUnbounded<int>();

    // ReadAsync + Publish で何回も待受可能になる
    var connectable = channel.Reader.ReadAllAsync().Publish();

    using (connectable.Connect())
    {
        // 複数回待受
        WaitForChannelAsync(connectable, this.GetCancellationTokenOnDestroy()).Forget();
        WaitForChannelAsync(connectable, this.GetCancellationTokenOnDestroy()).Forget();
        WaitForChannelAsync(connectable, this.GetCancellationTokenOnDestroy()).Forget();

        var writer = channel.Writer;

        writer.TryWrite(1);
        writer.TryWrite(2);
        writer.TryWrite(3);
        writer.Complete();
    }
}
```



## AsyncReactiveProperty

`AsyncReactiveProperty`は`UniRx`の`ReactiveProperty`の`UniTask`版です。
ベースとして`IUniTaskAsyncEnumerable<T>`が利用されています。
（`ReactiveProperty`はベースが`IObservable<T>`）

基本的な使い方は`ReactiveProperty`と変わりません。

```cs:AsyncReactiveProperty
private void Start()
{
    var token = this.GetCancellationTokenOnDestroy();

    // AsyncReactiveProperty生成
    var asyncReactiveProperty = new AsyncReactiveProperty<string>(null);

    // 待受開始
    WaitForAsync(asyncReactiveProperty, token).Forget();

    // Valueプロパティに値をセットすると
    // MoveNextAsync() が次に進む
    asyncReactiveProperty.Value = "Hello!";

    // Dispose()するとこのAsyncReactivePropertyが完了する
    asyncReactiveProperty.Dispose();
}

private async UniTaskVoid WaitForAsync(
    IReadOnlyAsyncReactiveProperty<string> asyncReadOnlyReactiveProperty,
    CancellationToken token)
{
    // Valueプロパティで現在値を取得可能
    var current = asyncReadOnlyReactiveProperty.Value;
    Debug.Log(current);

    // IUniTaskAsyncEnumerable<T>として扱える
    var result = await asyncReadOnlyReactiveProperty
        // null以外の値がセットされるのを待つ
        .FirstOrDefaultAsync(x => x != null, cancellationToken: token);

    Debug.Log(result);
}
```

### UniRx.ReactivePropertyとAsyncReactivePropertyの使い分け

結論から先にいうと次になります。

* イベントメッセージの通知として使う方が多いなら `UniRx.ReactiveProperty` を使う
* `await`で変化を待ち受けることの方が多いなら `AsyncReactiveProperty` を使う

----

`UniRx.ReactiveProperty`と`AsyncReactiveProperty`はそれぞれ次の点が異なります。

||`UniRx.ReactiveProperty<T>`|`AsyncReactiveProperty<T>`|
|:--|:--|:--|
|ベースの型|`IObservable<T>`|`IUniTaskAsyncEnumerable<T>`|
|ふるまい|`Push`|`Pull`|
|直接`await`した時の挙動|次の値に更新されるまで待つ（現在値は無視）|`WaitAsync`または`UniTask.Linq`を経由する必要あり|
|Operator/LINQをつないだときの挙動|`IObservable<T>`扱いになるので、`ToUniTask()`などが必要|`LINQ`の挙動による|
|得意なこと|メッセージのブロードキャスト|条件を指定しての柔軟な`await`|

`UniRx.ReactiveProperty<T>`は直接`await`できますが、そのときの挙動がコントロールできません。
たとえば「特定のパラメータに変化するまで待つ」といったことがしたい場合、`Operator`を経由する必要があります。

ただ`Operator`を使ってしまうと`IObservable<T>`に対する`await`になるため、取り扱いが面倒くさいことになります。

```cs:UniRx.ReactivePropertyのawait
private async UniTaskVoid WaitForPropertyAsync(ReactiveProperty<int> p, CancellationToken ct)
{
    // IObservableに対するawaitになる
    // CancellationTokenを指定するにはToUniTaskが必要でいろいろめんどくさい
    var result = await p.Where(x => x == 10).ToUniTask(useFirstValue: true, ct);
    Debug.Log(result);
}
```

`UniRx.ReactiveProperty<T>`は`await`とは相性があまり良くないですが、複数の購読者に対して一斉にメッセージを送ることについては抜群に得意です。

---

一方の`AsyncReactiveProperty<T>`は`IUniTaskAsyncEnumerable<T>`ベースのため、`await`と非常に相性がよいです。
素直にそのまま書くことができ、`CancellationToken`の指定も簡単です。

```cs
private async UniTaskVoid WaitForPropertyAsync(AsyncReactiveProperty<int> p, CancellationToken ct)
{
    // そのままLINQを書いてawaitできる
    // CancellationTokenも渡しやすい
    var result = await p.FirstAsync(x => x == 10, ct);

    // 非同期処理を間で挟むこともできる
    await p
        .SelectAwaitWithCancellation(async (x, t) =>
        {
            // 与えられた値のミリ秒分だけ待つ（何がしたいんだこの例）
            await UniTask.Delay(x, cancellationToken: t);
            return x;
        })
        .FirstAsync(cancellationToken: ct);
}
```

ただし`AsyncReactiveProperty<T>`は`await`のタイミングによっては値の取りこぼしが発生してしまいます。
そのため確実にメッセージをブロードキャストして相手に確実に届けるという用途には向いていません。
（その点は`UniRx.ReactiveProperty<T>`のほうが有利です）

```cs:取りこぼしが起きる例
private async UniTaskVoid WaitForPropertyAsync(AsyncReactiveProperty<int> p, CancellationToken ct)
{
    // UniRxのSubscribe感覚でForEachAwaitAsyncを使うと
    // メッセージの取りこぼしが起きうる
    await p.ForEachAwaitWithCancellationAsync(async (x, ct) =>
    {
        Debug.Log(x);
        // 1秒待ってから次の値を取りに行く
        // このawait中に変動した値は取りこぼす
        await UniTask.Delay(1000, cancellationToken: ct);
    }, ct);
}
```

取りこぼしの防止策として、後述する`Queue()`を使う方法もあります。


## UniTask.Linq

`IUniTaskAsyncEnumerable<T>`には`LINQ`メソッドが用意されています。
利用するためにはasmdef「`UniTask.Linq`」への参照を追加する必要があります。

使えるメソッドは次です。

### 同期型

* AggregateAsync
* AllAsync
* AnyAsync
* AsUniTaskAsyncEnumerable
* AverageAsync
* Buffer
* Append
* Prepend
* Cast
* Concat
* ContainsAsync
* CountAsync
* DefaultIfEmpty
* Distinct
* DistinctUntilChanged
* Do
* ElementAtAsync
* ElementAtOrDefaultAsync
* Except
* FirstAsync
* FirstOrDefaultAsync
* ForEachAsync
* GroupBy
* GroupJoin
* Intersect
* Join
* LastAsync
* LastOrDefaultAsync
* LongCountAsync
* MaxAsync
* MinAsync
* OfType
* OrderBy
* OrderByDescending
* Reverse
* Select
* SelectMany
* SequenceEqualAsync
* SingleAsync
* SingleOrDefaultAsync
* Skip
* SkipLast
* SkipWhile
* SkipUntil
* SumAsync
* Take
* TakeLast
* TakeWhile
* TakeUntil
* ToArrayAsync
* ToDictionaryAsync
* ToHashSetAsync
* ToListAsync
* ToLookupAsync
* ToObservable
* Union
* Where
* Zip
* CombineLatest
* Pairwise
* Queue
* SkipUntilCanceled
* TakeUntilCanceled
* Publish

### 非同期型

`AwaitAsync`がついているものは`async/await`を内部で利用できます。

* AggregateAwaitAsync
* AllAwaitAsync
* AnyAwaitAsync
* AverageAwaitAsync
* CountAwaitAsync
* DistinctAwait
* DistinctUntilChangedAwait
* DoAwait
* FirstAwaitAsync
* FirstOrDefaultAwaitAsync
* ForEachAwaitAsync
* GroupByAwait
* GroupJoinAwait
* JoinAwait
* LastAwaitAsync
* LastOrDefaultAwaitAsync
* LongCountAwaitAsync
* MaxAwaitAsync
* MinAwaitAsync
* OrderByAwait
* OrderByDescendingAwait
* SelectAwait
* SelectManyAwait
* SingleAwaitAsync
* SingleOrDefaultAwaitAsync
* SkipWhileAwait
* SumAwaitAsync
* TakeWhileAwait
* ToDictionaryAwaitAsync
* ToLookupAwaitAsync
* WhereAwait
* ZipAwait

こちらは`CancellationToken`を内部で必要とするときに使います。

* AggregateAwaitWithCancellationAsync
* AnyAwaitWithCancellationAsync
* AverageAwaitWithCancellationAsync
* CountAwaitWithCancellationAsync
* DistinctAwaitWithCancellation
* DistinctUntilChangedAwaitWithCancellation
* DoAwaitWithCancellation
* FirstAwaitWithCancellationAsync
* FirstOrDefaultAwaitWithCancellationAsync
* ForEachAwaitWithCancellationAsync
* GroupByAwaitWithCancellation
* GroupJoinAwaitWithCancellation
* JoinAwaitWithCancellation
* LastAwaitWithCancellationAsync
* LastOrDefaultAwaitWithCancellationAsync
* LongCountAwaitWithCancellationAsync
* MaxAwaitWithCancellationAsync
* MinAwaitWithCancellationAsync
* OrderByAwaitWithCancellation
* OrderByDescendingAwaitWithCancellation
* SelectAwaitWithCancellation
* SelectManyAwaitWithCancellation
* SingleAwaitWithCancellationAsync
* SingleOrDefaultAwaitWithCancellationAsync
* SkipWhileAwaitWithCancellation
* SumAwaitWithCancellationAsync
* TakeWhileAwaitWithCancellation
* ToDictionaryAwaitWithCancellationAsync
* ToLookupAwaitWithCancellationAsync
* WhereAwaitWithCancellation
* ZipAwaitWithCancellation


## 補足: Pushベースの`UniTaskAsyncEnumerable`の注意点

`IUniTaskAsyncEnumerable<T>`は`Pull`型として動作します。
そのためメッセージの受信準備が整い、内部でイテレータの`MoveNextAsync()`が実行されたタイミングで次のメッセージを取りに行きます。

ですが、`UniTaskAsyncEnumerable`ではメッセージ発行が`Push`な`IUniTaskAsyncEnumerable<T>`も存在します（ややこしい）

* `UniTask.EveryUpdate()`
* `AsyncReactiveProperty`
* `AsyncTrigger`より生成したもの
* `uGUI`のイベントなどから変換したもの

これらは`Push`されてきたイベントを`IUniTaskAsyncEnumerable<T>`として提供します。
そのため`MoveNextAsync()`とタイミングが合わなかった場合は、**その間に発行されたイベントは取りこぼされる**という点に注意が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47146/64540c36-0ca8-fc66-3296-ea885562fe98.png)


逆に、このイベントの取りこぼしを利用して処理を組むこともできます。

```cs:イベントの取りこぼしを利用した例
// uGUIのボタンの連打防止
// 1回ボタンを押したら2秒間無反応になる

// ForEachAwaitAsyncが待機中に発行されたメッセージは無視するという
// 性質を利用している
_button.OnClickAsAsyncEnumerable()
    .ForEachAwaitWithCancellationAsync(async (_, ct) =>
    {
        Debug.Log("Clicked!");
        await UniTask.Delay(2000, cancellationToken: ct);
    }, token);
```



### 取りこぼしが嫌なら Queue() を使おう

`Queue`は`UniTask.Linq`が提供する`LINQ`メソッドの1つです。

`IUniTaskAsyncEnumerable<T>`に対して先に`MoveNextAsync()`を実行し、その結果をキューに詰めて再度`IUniTaskAsyncEnumerable<T>`として提供します。
（`Observable`でいうところの`Publish()`に相当します）

`Queue`を使えばイベントの取りこぼしを防ぐことができるため、必要に応じて利用しましょう。

```cs:Queue
private void Start()
{
    var token = this.GetCancellationTokenOnDestroy();

    // AsyncReactiveProperty生成
    var asyncReactiveProperty = new AsyncReactiveProperty<string>("Initialize!");

    // 待受開始
    WaitForAsync(asyncReactiveProperty, token).Forget();

    // 値を設定
    asyncReactiveProperty.Value = "Hello!";
    asyncReactiveProperty.Value = "World!";
    asyncReactiveProperty.Value = "Thank you!";

    asyncReactiveProperty.Dispose();
}

private async UniTaskVoid WaitForAsync(
    IReadOnlyAsyncReactiveProperty<string> asyncReadOnlyReactiveProperty,
    CancellationToken token)
{
    // AsyncReactiveProperty はキューイングしてくれないので
    // タイミングによってはメッセージの取りこぼしが置きうる
    // 取りこぼしが嫌ならQueueを併用する
    await asyncReadOnlyReactiveProperty
        .Queue() // Queueを挟む
        .ForEachAwaitWithCancellationAsync(async (x, ct) =>
        {
            Debug.Log(x);
            // 1秒待って次の値を取りに行く
            await UniTask.Delay(1000, cancellationToken: ct);
        }, token);
}
```

# まとめ

## UniTask2の感想

`C# 8.0`の`IAsyncEnumerable`相当の処理をいち早く`Unity`でも利用できるため、かなり期待が高いアップデートとなりました。
`UniTaskAsyncEnumerable`と`AsyncReactiveProperty`が刺さる人にはかなり刺さる機能でしょう（自分はさっそく使いたい）

## UniRx（Observable）との使い分け

ぶっちゃけ、**非同期処理の用途としてUniRx（Observable）が選択肢に上がってくることがなくなりました。**
`async/await` + `UniTask` / `UniTaskAsyncEnumerable` でほとんどの非同期処理のシチュエーションはカバーできてしまいます。

じゃあ`Observable`はお役御免かというとそうではなく、「イベント駆動」として使う方ではまだまだ活用が可能です。
特にイベントメッセージが飛び交うゲーム開発では`Observable`が活躍するシチュエーションは多いでしょう。

とくに`ReactiveProperty`と`AsyncReactiveProperty`の使い分けが特に今後は重要になってくるかなと思います。