UniTaskを使用した非同期処理の実装方法

UniTaskとは

 Unityで非同期処理を行う場合、最初に学ぶのはコルーチンですが、Unitaskというライブラリを使うとコルーチンよりもコードを簡潔に書いて非同期処理を実行できます。また、便利な機能も豊富なので慣れてきたら積極的に活用したいですね。パフォーマンスの面でもGB(ガーベジコレクション)が少なくて処理が軽いです。
注意点としてキャンセル処理を適切に行わなければ中断をされない場合があります。

非同期処理とは

ロード画面など時間がかかる処理でも、ゲームの画面表示や進行を止めずに、一定時間経過後に処理を行いたい場合に非同期処理を使用します。

Unitask準備

Unitaskで汎用的に使える機能

Unitaskにはいくつか機能がありますので、使用頻度の高そうな部分をこちらに載せていきます。

await Unitask.Delay

一定時間を待って演出を入れたり、遅延実行をするときによく使用する。

// UniTask.Delay
    private async UniTask sample1()
    {
        message = "3";
        await UniTask.Delay(TimeSpan.FromSeconds(1));
        //await UniTask.Delay(UniTask.Delay(1000));

        message = "2";
        await UniTask.Delay(TimeSpan.FromSeconds(1));

        message = "1";
        await UniTask.Delay(TimeSpan.FromSeconds(1));
    }

 

ペンギンさん
UniTask.Delayでも指定できるけど、待機時間を秒単位で見やすく書くにはTimeSpan.FromSecondsの方がおすすめだよ!
UniTask.DelayFrameというフレーム単位でも指定はできるよ!

await UniTask.Yield

処理をちょうど1フレームだけ遅らせたい場合に使用する。
UniTask.Delayよりも軽量で、例えばStart()の処理内で初期化した後に、1フレーム後にUIを表示する場面などに有効。

// UniTask.Yield
    private async UniTask sample2()
    {
        message = "1";
        await UniTask.Yield();

        message = "2";
        await UniTask.Yield();

        message = "3";
        await UniTask.Yield();
    }

await SceneManager.LoadSceneAsync(“シーン名”).ToUniTask()

ToUnitask()を付けて、awaitすることでシーンの読み込み終了後にフェードインやフェードアウトなどの追加の処理を実行できる。
通常のLoadSceneAsyncでは読み込み完了と同時にシーンが切り替わるため、演出が挟めないので演出を入れる場合に有効。

// SceneManager.LoadSceneAsync
    private async UniTask sample3()
    {
        message = "シーン遷移スタート";
        await SceneManager.LoadSceneAsync("UnitaskTest2").ToUniTask();
        message = "シーン遷移完了";
    }

UniTask.WhenAll(処理1,処理2,…)

複数タスクを同時に並列実行して、全て完了するまで待機をする。引数は最大16個。
タスクは1つずつ実行ではなく、並列で実行をして待機をする。
例として、ロード画面でUIアニメーションとデータ取得を並列に実行して、両方終わったら次に進むなどで使える。

// UniTask.WhenAll
    private async UniTask sample3()
    {
        await UniTask.WhenAll(task1(), task2());
        message = "Finish";
    }

    private async UniTask task1()
    {
        message = "task1";
        await UniTask.Delay(TimeSpan.FromSeconds(2));
    }

    private async UniTask task2()
    {
        message = "task2";
        await UniTask.Delay(TimeSpan.FromSeconds(2));
    }

UniTask.WhenAny(処理1,処理2,…)

先に完了したタスクが返るまで待機をする。
例として、ゲームで制限時間以内にボタンが押されたかどうか判定をする。httpリクエストを複数実行して、先に処理ができた方を実行する。複数ボタンがある中でどれか押されるまで待機するなど。

// UniTask.WhenAny
    private async UniTask sample5()
    {
        message = "待機中";

        // 時間経過タスク(5秒)
        var timeoutTask = UniTask.Delay(TimeSpan.FromSeconds(5));

        // キーボード入力タスク(スペースキー)
        var keyInputTask = WaitForSpaceKey();

        // どちらが先に完了するかを判定
        var result = await UniTask.WhenAny(timeoutTask, keyInputTask);

        if (result == 0)
        {
            message = "アウト";
        }
        else
        {
            message = "ゲームクリア";
        }

        // 結果を少し表示してからリセット
        await UniTask.Delay(TimeSpan.FromSeconds(2));
        message = "";
    }

    private async UniTask WaitForSpaceKey()
    {
        while (true)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                break;
            }
            await UniTask.Yield();
        }
    }

CancellationToken

※2025/08/15更新

コルーチンと異なり、unitaskの非同期処理は実行して走らせたままにすると、シーンが切り替わったり、オブジェクトが破棄されても処理が続いてしまう場合があります。そこでキャンセル処理をあらかじめ用意しておく必要があります。

キャンセルの流れ
– キャンセルを可能にするにはCancellationTokenを返す
– キャンセルの発生源はcancellatonTokenSource
– Cancelを呼ぶとそのトークンを持っている全てのタスクが中断される
– 中断されたタスクはOperationCanceledExceptionを投げる

public async UniTask AnimateAsync(CancellationToken token)
    {
        try
        {
            // 1: 500ms待って点滅
            await UniTask.Delay(500, cancellationToken: token);
            // ここでオブジェクト操作するなら Destroy 連動トークン推奨
            transform.localScale = Vector3.one * 1.1f;

            // 2: 200ms待って戻す
            await UniTask.Delay(200, cancellationToken: token);
            transform.localScale = Vector3.one;

            // 3: 300ms待って色変更
            await UniTask.Delay(300, cancellationToken: token);
            // ...続く
        }
        catch (OperationCanceledException)
        {
            // キャンセル時の後始末(中途半端を戻す等)
        }
    }

    // 呼び出し例(破棄で自動キャンセル)
    private async UniTaskVoid Start()
    {
        var token = this.GetCancellationTokenOnDestroy();
        await AnimateAsync(token);
    }

async UniTask<T>

コルーチンでは戻り値を返せないが、UniTask<T>であれば、非同期完了時に値を返却ができる。
例として、ボタンの選択結果やスコア計算後の結果を返却する際に使用ができる。

// UniTask
    private async UniTask sample7()
    {
        await UniTask.Delay(TimeSpan.FromSeconds(2));
        return "Hello";
    }

async UniTaskVoid

UnitaskVoidは戻り値も例外通知も行わない特殊なもの。
以下のようにUnity標準のイベント処理ではUnitaskは使用できないので、Start()などのイベント処理で非同期処理を呼び出したいときに限定的に使用をする。

// UniTaskVoid
    private async UniTaskVoid Start()
    {
        await UniTask.Delay(TimeSpan.FromSeconds(2));
        message = "ゲームスタート!";
    }
//これは実行できない
private async UniTask Start()
{
    await UniTask.Delay(1000);
    Debug.Log("スタート!");
}

まとめ

標準のコルーチンよりもコードの可読性が向上するので、私も積極的に使おうと思います。Unitaskにはこれ以外にも機能があるので場面に合わせて使い分けれるといいかもしれません。

最新情報をチェックしよう!
>
CTR IMG