『UniTask v2』の色々な .ToUniTask

# はじめに

UniTask にはあらゆるものを UniTask に変換する `.ToUniTask`というメソッドがあります
同じものを変換するときでもオーバーロードがあったりでややこしいので備忘録としてまとめてみました
`GetAwaiter` や `WithCancellation` なども書いておきます(実装を書くのは省きます)


[UniTask/Github](https://github.com/Cysharp/UniTask)

# 環境

以下の環境で動作しています

- Unity 2019.4.15f1
- UniTask 2.0.37

# 目次
- [CancellationToken](#CancellationToken)
- [コルーチン](#コルーチン)
- [AsyncOperation](#AsyncOperation)
- [IObservable](#IObservable)
- [JobHandle](#JobHandle)



# 準備

```C#:ダミー変数
var token = this.GetCancellationTokenOnDestroy();
var uniTask = UniTask.Run(() => "hoge");
```

こんな感じで変数`token`は何かしらの`CancellationToken`が、
変数`uniTask`には何かしらのUniTaskが入っていると考えてください

# CancellationToken

`CancellationToken` を UniTask に変換できます

### ToUniTask

```C#
// 実装
public static (UniTask, CancellationTokenRegistration) ToUniTask(this CancellationToken cancellationToken)

// 使用例
var (task,registration) = token.ToUniTask();
registration.Dispose(); // CancellationTokenRegistrationから破棄もできる
await task;
```

CancellationToken が**キャンセル状態になるまで await される** UniTask が返ってきます
返り値は `(UniTask, CancellationTokenRegistration)` というタプルです

### WaitUntilCanceled

UniTask型ではありませんが、awaitableなCancellationTokenの型が返ってきます

```C#
// 実装
public static CancellationTokenAwaitable WaitUntilCanceled(this CancellationToken cancellationToken)

// 使用例
await token.WaitUntilCanceled(); // 直接awaitできる。ToUniTaskはできない
```
### 逆変換

UniTask を CancellationToken に変換できます

```C#
// 実装
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task)
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task, CancellationToken linkToken)

// 使用例
var ct = uniTask.ToCancellationToken();
var ct2 = uniTask.ToCancellationToken(linkToken);
```

# コルーチン

```C#:ダミーのコルーチン
private IEnumerator HogeCoroutine(){
    yield break;
}
```

### ToUniTask , GetAwaiter , WithCancellation

UniTask では **`StartCoroutine` を使わずにコルーチンを起動**することができます
ただし、UniTaskはコルーチンの `yield return new WaitForEndOfFrame()`を再現できないので注意が必要です。

```C#
// 実装
public static UniTask ToUniTask(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

// 使用例
await HogeCoroutine(); // コルーチン起動
await HogeCoroutine().ToUniTask(PlayerLoopTiming.FixedUpdate, token);
await HogeCoroutine().WithCancellation(token);
```

下記は `MonoBehaviour` を引数に渡すことで内部で`StartCoroutine`が走ります。なので `yield return new WaitForEndOfFrame()`も正常動作しますし、寿命もゲームオブジェクトと結びついてくれます。


```C#
// 実装
public static UniTask ToUniTask(this IEnumerator enumerator, MonoBehaviour coroutineRunner)

// 使用例
await HogeCoroutine().ToUniTask(this);
```

### 逆変換

UniTask を IEnumerator に変換できます

```C#
// 実装
public static IEnumerator ToCoroutine<T>(this UniTask<T> task, Action<T> resultHandler = null, Action<Exception> exceptionHandler = null)
public static IEnumerator ToCoroutine(this UniTask task, Action<Exception> exceptionHandler = null)

// 使用例
StartCoroutine(uniTask.ToCoroutine(Debug.Log,Debug.LogError));
StartCoroutine(uniTask.ToCoroutine(Debug.LogError));
```

エラーハンドリングや結果を得るためのデリゲートを渡せるオーバーロードが用意されてます

# AsyncOperation

`AsyncOperation` を UniTask に変換できます

UniTask には 様々な AsyncOperation が awaitable になっています。

- AsyncOperation
- ResourceRequest
- AssetBundleRequest
- AssetBundleCreateRequest
- UnityWebRequest

シグネチャは拡張メソッドのthisキーワードが付いた引数の型の違いしかなく、使い方は全部一緒なので省きます。

```C#
// 実装
public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

// 使用例
await SceneManager.LoadSceneAsync("Game");
await SceneManager.LoadSceneAsync("Game").ToUniTask(cancellationToken : token);
await SceneManager.LoadSceneAsync("Game").WithCancellation(token);
await SceneManager.LoadSceneAsync("HogeScene")
                  .ToUniTask(Progress.Create<float>(n => {
                      Debug.Log($"現在{n * 100}%");
                  }), PlayerLoopTiming.Update, token);
```

`SceneManager.LoadSceneAsync` の例です。
.ToUniTaskに `Progress` を入れて進行状況を取ることができます。

# AsyncLazy

UniTask に用意されている `AsyncLazy型`です。ToUniTask ではなく `.Taskプロパティ` から取得できます

### .Taskプロパティ

```C#
var lazy = UniTask.Lazy(() => uniTask);
var task = lazy.Task; // UniTaskに変換
```

### 逆変換

UniTask から AsyncLazy に変換できます


```C#
uniTask.ToAsyncLazy(); // AsyncLazyに変換
```

# IObservable

`IObservable` を UniTask に変換できます。

### ToUniTask

```C#
// 実装
public static UniTask<T> ToUniTask<T>(this IObservable<T> source, bool useFirstValue = false, CancellationToken cancellationToken = default)

// 使用例
await subject.ToUniTask(cancellationToken:token); // OnCompletedが発行されるまで待つ
await subject.ToUniTask(true,token); //次に発行される最初のメッセージを待つ
```

第一引数を`true`にすると、次に発行される**最初のメッセージだけ**を待機できます。

### 逆変換

UniTask を IObservable に変換できます。

```C#
uniTask.ToObservable();
```

# JobHandle

ジョブの実行が終了するまで待機できます

```C#
// 実装
public static UniTask ToUniTask(this JobHandle jobHandle, PlayerLoopTiming waitTiming)
public static async UniTask WaitAsync(this JobHandle jobHandle, PlayerLoopTiming waitTiming, CancellationToken cancellationToken = default)

// 使用例
await jobHandle;
 // CancellationTokenが指定できない
await jobHandle.ToUniTask(PlayerLoopTiming.FixedUpdate);
 // 指定したタイミングに切り替えてjobHandle.Complete();される
await jobHandle.WaitAsync(PlayerLoopTiming.FixedUpdate,token);
```

実装を見ればどれも違うのがわかりますが、自分はJobHandleに詳しくないので有識者の方、コメントや編集リクエストをお願いします！

# AsyncGPUReadbackRequest

`AsyncGPUReadbackRequest` を UniTask に変換できます。

### ToUniTask

```C#
// 実装
public static UniTask<AsyncGPUReadbackRequest> ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))

await AsyncGPUReadback.Request(src);
await AsyncGPUReadback.Request(src).ToUniTask(cancellationToken : token);
await AsyncGPUReadback.Request(src).WithCancellation(token);
```

### 逆変換

UniTask を AsyncGPUReadbackRequest に変換できます

```C#
var asyncGpuReadbackRequest = AsyncGPUReadback.Request(src);
asyncGpuReadbackRequest.ToUniTask(PlayerLoopTiming.FixedUpdate,token);
```

# おわりに

文章書くの下手すぎて読みにくいかもです。すいません。
間違いがあったら優しく教えてください。

# 変更履歴

### 変更1(コルーチン)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">IEnumerator.ToUniTask()は何も指定しないと「コルーチンっぽく動く」だけなので yield return new WaitForEndOfFrame(); は正しく動かない。<br><br>IEnumerator.ToUniTask(MonoBehaviour)は内部でStartCoroutineするから本物のコルーチンが起動するのでちゃんと動作する。</p>&mdash; とりすーぷ (@toRisouP) <a href="https://twitter.com/toRisouP/status/1346695109266444288?ref_src=twsrc%5Etfw">January 6, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

とりすーぷさんからご指摘をいただきコルーチンの項目を少し書き換えました！ありがとうございます！