【Unity】UniTask入門-Part2 UniTaskとはなんぞや!

UniTask入門-part2 UniTaskとはなんぞや!Tips

こんにちは!saba🐟です!

前回はUniTaskを学ぶ上での必要な知識についておさらいしました。今回はついにUniTaskについてまとめていきたいと思います!UniTaskでよく使いそうな機能に焦点を置いてまとめていきます!

それでは早速やっていきましょう!

今回UniTaskについて調べていく中で細心の注意は払いますが、間違った知識を記載してしまう可能性があります。その点ご了承いただいたうえで見ていただけると大変助かります!
(もし間違いを見つけたら、コメントやお問い合わせなどで教えていただけるとありがたいです!)

本記事を見る前に…

本記事はUniTask入門のPart2となっています。「非同期処理とはなんぞや!!」という方や「チョットヨクワカラナイ・・」という方は、前回の記事を見てから本記事を見ることをお勧めします。逆に、非同期についてカンペキという方は飛ばしてもらっても大丈夫です!

UniTaskとは

まずはUniTaskとはなんぞや!というところから話していきます。

UniTaskとはUniRxの姉妹ライブラリで、Cygamesの子会社のCysharpが提供しています。元々はUniRxの中に入っていた機能なのですが、現在は独立したライブラリとして使用することができます。

UniTaskのライブラリの中にはUnity向けに最適化されたTaskの「UniTask」や、UnityのAPIをasync/awaitに対応したものなどが含まれています。前回の記事で少し話したUnity標準のTaskのせいでasync/awaitが使いづらい問題なども解決してくれています。

UniTaskの機能

次に、UniTaskに含まれている機能の中で基本的な部分や、よく使用する機能について紹介していきます。

UniTask/UniTask<T>

まずはUniTaskでの一番基本的なオブジェクトとしてUniTask/UniTask<T>というものがあります。

前回の記事で紹介したTaskのUnity向けのものになっていて、これを使用することによってコストを考えずにTaskを使用できるようになります。
標準のTaskを使用せずにこちらのUniTask/UniTask<T>に置き換えることで色々な欠点が解消されます。何か理由がない場合はUniTask/UniTask<T>に置き換えるようにしましょう。

標準Taskと比べて良い点をまとめて見ました。

オブジェクトのサイズが小さい
  標準のTaskはマルチスレッド機能を含んでしまっているので肥大化している。

ゼロアロケーション
  UniTaskはclassではなくstruct(構造体)で作られているのでヒープをほとんどの場合確保しない。

標準のTaskよりパフォーマンスを出せる

UniTaskの簡単な例

以下UniTaskを使用した簡単なDelay処理の例です。Debug.Logで実行タイミングを確認していきます。

private void Start()
{
    Debug.Log("DelayAsyncを呼び出すよ!");
    DelayAsync(2).Forget();
    Debug.Log("DelayAsyncの呼び出しが完了したよ!");
}

private async UniTaskVoid DelayAsync(int delayTime)
{
    Debug.Log("DelayAsyncが開始したよ!");
    await UniTask.Delay(TimeSpan.FromSeconds(delayTime));
    Debug.Log($"{delayTime}秒経過!Delayが終了したよ!");
}

このプログラムを実行すると上記スクショのような結果になります。

呼び出し終了したよ!というlogが先に実行されてその後、12行目の処理が2秒後に表示されていることがわかると思います。

Preserve()

UniTaskでは2回以上同じインスタンスをawaitできないというルールがあります。

もし、2回以上awaitしなくてはならない場合は、Preserve()を呼ぶようにしましょう。

Preserve()をつけない場合

まずはPreserve()を使わずに2回実行してみましょう

private async void Start()
{
    var delayAsync = DelayAsync(2);
    await delayAsync;//1回目の実行
    await delayAsync;//2回目の実行
}

private async UniTask DelayAsync(int delayTime)
{
    Debug.Log("DelayAsyncが開始したよ!");
    await UniTask.Delay(TimeSpan.FromSeconds(delayTime));
    Debug.Log($"{delayTime}秒経過!Delayが終了したよ!");
}

preserveを使わずに2回目の実行をしようとすると、このように例外が発生してしまいます。
そのため2回目を実行したい場合は、Preserve()をつけましょう。

Preserve()をつけた場合
private async void Start()
{
    Debug.Log("Start()を開始したよ!!");
    var delayAsync = DelayAsync(2).Preserve();
    await delayAsync;//1回目の実行
    await delayAsync;//2回目の実行(完了済みなので素通りする)
    Debug.Log("Start()を終了するよ!");
}

private async UniTask DelayAsync(int delayTime)
{
    Debug.Log("DelayAsyncが開始したよ!");
    await UniTask.Delay(TimeSpan.FromSeconds(delayTime));
    Debug.Log($"{delayTime}秒経過!Delayが終了したよ!");
}

このようにpreserve()を入れることでエラーがなくなりました!

しかし、自分はこの処理をしたときに、2回DelayAsyncの中を通ってから「Start()を終了するよ!」がでると思ってたのですが、違うみたいですね!
完了済みのdelayAsyncを渡しているから素通りするみたいです。

キャンセル処理について(CancelationToken)

UniTaskはGameObjectを削除しても動き続けてしまいます。そのため、キャンセル処理を書いてあげましょう。

awaitをする際にUniTaskにCancelationTokenというオブジェクトを送ってあげるようにします。
このキャンセル処理を書かないと、GameObjectが破棄されたときや非アクティブ状態になったときに、Taskが消えないのでScene移動したときなどにObjectが残ってしまったりエラーが出てしまったり良くないことが起きてしまいます。

それを防ぐために、UniTaskを使う時は必ずキャンセル処理を使用するようにしましょう!

具体的なキャンセルの方法は、キャンセル処理を行いたいタイミングで、CancellationTokenSource.Cancel()を送ってあげることでキャンセル可能です。

生成したtokenはバケツレースのようにしっかり下層まで送ってあげてください。

以下のプログラムは待機する処理をspaceキーでキャンセルできるようにしたものです。

private CancellationTokenSource tokenSource;

//トークンの生成と処理の開始。
private async void Start()
{
    tokenSource = new CancellationTokenSource();
    await DelayAsync(5,tokenSource.Token);
}

// SpaceキーでキャンセルできるようにしたUpdate文
private void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        tokenSource.Cancel();
    }
}

// Delay用の非同期処理
private async UniTask DelayAsync(int delayTime,CancellationToken token)
{
    Debug.Log($"{delayTime}秒待ちます!");
    await UniTask.Delay(TimeSpan.FromSeconds(delayTime),cancellationToken:token);
    Debug.Log($"処理が終了しました!");
}

このようにキャンセル処理を送ってあげないといけないので少し面倒ではありますが、UniTaskをしようする場合はしっかり、キャンセルする処理を書くようにしましょう!

コルーチンから置き換えてみよう

UniTaskにはコルーチンと同等の機能が用意されているので元々コルーチンを使用していた人からすると馴染みのあるメソッド名で使用することができますので早速みていきましょう!

コルーチンと同等の機能

1フレーム待つ

コルーチンの場合

private IEnumerator OneFrameWait()
{
    yield return null;
}

UniTaskの場合

private async UniTask OneFrameWaitUniTask(CancellationToken token)
{
    await UniTask.Yield(token);  
}

1秒待つ

コルーチンの場合

private IEnumerator OneMinutesWaitCoroutine()
{
    yield return new WaitForSeconds(1);
}

UniTaskの場合

private async UniTask OneMinutesWaitUniTask(CancellationToken token)
{
    await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: token);
}

条件を満たすまで待つ

コルーチンの場合

private IEnumerator WaitUntilCoroutine()
{
    yield return new WaitUntil(()=> Input.GetKeyDown(KeyCode.Space));
}

UniTaskの場合

private async UniTask WaitUntilUniTask(CancellationToken token)
{
    await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space), cancellationToken: token);
}

条件を満たさなくなるまで待つ

コルーチンの場合

private IEnumerator WaitWhileCoroutine()
{
    yield return new WaitWhile(()=> isWait);
}

UniTaskの場合

private async UniTask WaitWhileUniTask(CancellationToken cancellationToken)
{
    await UniTask.WaitWhile(() => isWait, cancellationToken: cancellationToken);
}

uGUIのイベントを待つ

普段Unityで使用するOnClickなどの関数もawaitすることができます。

以下のプログラムの例はmButtonを押したときのOnClickという関数をawaitで待つプログラムです。

private async UniTaskVoid WaitOnButtonClick(CancellationToken token)
{
    await button.OnClickAsync(token);
}

他のイベントも、On〇〇Asyncの形で呼び出すことができます。

UniTaskTracker

UniTaskTrackerは現在動いているUniTaskの項目や状態などの情報を確認できるものです。

元々のTaskではTaskを生成したあと、動作中のTaskをUnity上から確認することができなかったのですが、UniTaskを使用する場合Trackerがあるので確認ができるようですね!便利!

[window]→[UniTask Tracker]
  • 稼働中のUniTask
  • UniTaskの稼働時間
  • どのクラスのどの行で動いているかなどを確認することができます。

初期状態では何も表示されないので[Enable AutoReload] [Enable Tracking][Enable StackTrace]をクリックしておきましょう!

キャンセルを忘れるとメモリリークが起きてしまう場合もあるのでうまく動かない場合だけではなく、こまめにチェックするようにしましょう!

まとめ

今回はUniTaskの使いそうな機能や便利な機能をまとめてみました。

自分自身今回UniTaskについて調べてみて、自分のゲームなどに導入してみたのですが、とても便利で慣れてしまえば手放せないライブラリになると感じました。

UniTaskは機能が豊富でまだまだ便利な機能がたくさんありますので、今回の記事で興味を持ってくれた方はぜひ自分でも調べてみてください。

ではでは!

コメント

タイトルとURLをコピーしました