渋谷ほととぎす通信

「Unityをわかりやすく」初心者のためのゲーム作りブログ

【Unity】UniTaskのキャンセルまとめasync/awaitを使いこなそう

【Unity】UniTaskのキャンセルまとめasync/awaitを使いこなそう

こんにちは、Unityエンジニアのオオバです。

お悩みさん
お悩みさん
  • UniTaskのキャンセルがよくわからない
  • コルーチンのキャンセル方法と違う?
  • UniTaskのキャンセルを理解したい
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    Unityをお使いのみなさまasync/await便利ですよね。コルーチンと比べてコードがスッキリ しました。
    今やUnityで非同期処理の実装は UniTask一択 といっても過言ではありません。

    オオバも ゲーム開発の現場でコルーチンを使うことは一切なくなりました。 Unity初心者やプログラミング初心者に教えるときに少しだけコルーチンを紹介する程度です。

    そもそもasync/await登場前の非同期処理は コルーチン でした。
    もちろん今でもコルーチンは使えますし、古いタイトルの運用にコルーチンを使っている現場はあると思います。

    しかし、コルーチンと比べて UniTaskのキャンセル処理が難しい という声をよく聞きます。
    そうなんです。UniTaskのキャンセルはコルーチンより少し面倒くさいです。

    そこで本記事では UniTaskのキャンセル処理について初心者向けにわかりやすく解説 します。
    UniTaskにおいてキャンセル処理は鬼門 なので気合を入れて丁寧に説明していきますね!ぜひ、みなさんもついてきてください。

    ↓そもそもUniTaskを始めたことがない方は、まずはこちらの記事を参考にすることをオススメします。

    👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

    なぜUniTaskのキャンセル処理が必要なのか?

    ゲーム開発をしていく上で処理のキャンセルは必須です。キャンセル処理を実装するポイントは大きく2点です。

    1. 演出のスキップ
    2. エラー発生時に後続処理をスキップ

    1.演出のスキップ

    演出のスキップは必ずと言ってよいほど実装します。具体的にはガチャアニメーションなどのスキップです。ガチャアニメーション中、ユーザーのタップでスキップすると思います。

    ユーザーが画面をタップ、またはスキップボタンを押した瞬間、特定のポイントにジャンプしますよね。具体的には演出後に表示される結果画面です。つまり、結果画面までのアニメーション処理をスキップする必要があるのです。

    2.エラー発生時に後続処理のスキップ

    エラー発生時に後続処理のスキップは アプリを安定稼働させるうえで必須事項 です。

    例えば、画面を表示させるとき通信エラーが起きたとします。その後に行う予定だった「画像のロード」や「インスタンスの生成」をキャンセルして、エラーダイアログを表示させる必要があるといった感じです。

    つまりUniTaskのキャンセルは開発する上で知っておかねば、思わぬ不具合をもたらし、最悪アプリがクラッシュします。
    この記事では、UniTaskのキャンセルを丁寧に解説しているので、ぜひ最後まで読んでマスターしてみてください。

    結論UniTaskのキャンセルにはCancellationTokenを使う

    結論から話すと、UniTaskのキャンセルには CancellationToken を使います。

    オオバ
    オオバ
    いきなり「CancellationToken」と言われても。。。という感じかもしれません。

    詳しい解説は後ほどしますが、ここでは簡潔にまとめます。

    • UniTaskはGameObjectのOFFでは止まらない(キャンセルできない)
    • UniTaskはCancellationTokenで停止する
    • 全てのUniTaskにCancellationTokenは引き渡したほうがいい

    重要なことなので何度も言いますが、コルーチンと違ってUniTaskはGameObjectのOFFでは止まりません。

    オオバ
    オオバ
    正しくUniTaskのキャンセルを理解していないと事故ります(ました😱)

    次の章からはUniTaskのキャンセル処理について具体的に解説していきます。

    UniTaskはGameObjectのOFFでは止まらない

    コルーチンからUniTaskに切り替えた時、最初につまづく点は GameObjectにUniTaskがひも付かない点 です。
    次のソースコードを見てみましょう。UniTask実行直後にGameObjectを非アクティブにしたサンプルです。

    💻ソースコード : UniTaskがGameObjectのOFFで停止しないサンプル
    async void Start()  
    {
        TestUniTask();  
        gameObject.SetActive(false);  
    }
    
    async UniTask TestUniTask()  
    {
        await UniTask.Delay(1000);  
        Debug.Log("Unitask完了");  
    }
    

    UniTask実行直後にGameObjectを非アクティブにしています。コルーチンなら止まっていたわけですが、UniTaskは止まりません。

    【Unity】UniTaskのキャンセルまとめasync/awaitを使いこなそう_0

    このように 1秒後のログはしっかり出力 されます。つまりUniTaskとGameObjectは分離して考える必要があるということです。

    ↓UniTaskの文法がよくわからないという方は、次の記事を参考にしてみてください。

    UniTaskはCancellationTokenで停止する

    結論から話すと、具体的なUniTaskのキャンセル方法は 「CancellationTokenSourceのCancelメソッドを呼ぶ」 です。

    UniTaskのキャンセルとは、非同期処理をするUniTaskの停止 を指します。まずはUniTaskを停止させるサンプルコードから見ていきましょう。

    UniTaskのキャンセルサンプル
    async Start()  
    {
        // CancellationTokenSourceの生成  
        var cts = new CancellationTokenSource();  
    
        // CancellationTokenをCancellationTokenSourceから取得  
        CancellationToken token = cts.token;  
    
        // UniTaskにTokenを引き渡す  
        Wait5SecUniTask(token);  
    
    
        await UniTask.Delay(TimeSpan.FromSeconds(1));  
        // 1秒後にキャンセルを実行  
        cts.Cancel();  
    }
    
    // 5秒後に「Complete!」とログを出力するUniTask  
    async UniTask Wait5SecUniTask(CancellationToken token)  
    {
        await UniTask.Delay(TimeSpan.FromSeconds(5), token);  
        Debug.Log("Complete!");  
    }
    

    大事なのでもう一度言いますが、UniTaskのキャンセル方法は 「CancellationTokenSourceのCancelメソッドを呼ぶ」 です。
    つまり以下の箇所で、UniTaskのキャンセル処理が実行されているということになります。

    cts.Cancel();  
    

    ここで新しく登場した「CancellationToken」と「CancellationTokenSource」について解説していきます。

    CancellationTokenSourceから生まれるCancellationToken

    CancellationTokenCancellationTokenSourceはUniTaskのキャンセルに必要な要素です。

    var cts = new CancellationTokenSource();  
    CancellationToken token = cts.token;  
    

    ↑コードのとおりCancellationTokenは、CancellationTokenSourceから生成します。

    cts.Cancel();  
    

    ↑UniTaskをキャンセルするときは、CancellationTokenSourceCancelメソッド を呼びます。

    ↓例えば UniTask.Delay()は以下のように第2引数にCancellationTokenをセットすることでキャンセル処理に対応可能です。

    await UniTask.Delay(1000, token);  
    

    つまり、CancellationTokenをセットしないとUniTaskのキャンセル処理は実装できないということです。

    UniTask(async/await)の世界を渡り歩くためには 「CancellationToken」と「CancellationTokenSource」の理解は必須です。
    あいまいな状態にしておくと 思わぬエラー、メモリリークなど を引き起こします。ぜひこの機会にマスターしておきましょう。

    複数のUniTaskを同時にキャンセルさせる

    CancellationTokenCancellationTokenSourceの理解を深めるために複数のUniTaskを同時にキャンセルさせるサンプルを作ってみます。

    複数のUniTaskを同時にキャンセルさせたい場合は、 1つのCancellationTokenSourceから作ったCancellationTokenをそれぞれのUniTaskに渡します。

    💻ソースコード : 複数UniTaskの同時キャンセル
    async void Start()  
    {
        // CancellationTokenSourceの生成  
        var cts = new CancellationTokenSource();  
    
        // CancellationTokenの取得  
        CancellationToken token = cts.token;  
    
        // Tokenを複数のUniTaskに引き渡す  
        TestUniTask(1, token);  
        TestUniTask(2, token);  
        TestUniTask(3, token);  
        TestUniTask(4, token);  
    
    
        await UniTask.Delay(TimeSpan.FromSeconds(1));  
        // 1秒後にキャンセル処理を実行  
        cts.Cancel();  
    }
    
    // 5秒後にログ出力するUniTask  
    async UniTask TestUniTask(int id, CancellationToken token)  
    {
        await UniTask.Delay(TimeSpan.FromSeconds(5), token);  
        Debug.Log($"UniTask : {id} 完了");  
    }
    

    このコードを実行すると、本来5秒後にログを出力する4つのUniTaskが、1秒後に全てキャンセルされます。
    CancellationTokenをひもづけたUniTaskはすべてキャンセル可能なUniTaskになる のです。

    なんとなくわかってきたのではないでしょうか。

    UniTaskキャンセル後の処理の分岐方法

    UniTaskのキャンセルは理解できたと思います。では次に キャンセル後の分岐 について解説します。
    どういうことかというと キャンセルしたときだけ呼びたい処理があった場合の対応 です。

    結論からいうと、try-catchを使います。
    ここで重要なのは UniTaskのキャンセルとは例外 だということです。

    次のコードを見てみましょう。

    💻ソースコード : UniTaskのキャンセル分岐サンプル
    CancellationTokenSource _cts;  
    
    async void Start()  
    {
        _cts = new CancellationTokenSource();  
        try {  
            await TestUniTask(_cts.token);  
            Debug.Log("UniTask完了");  
        }
        catch (OperationCanceledException e){  
            // キャンセル時に呼ばれる例外  
            Debug.Log("キャンセル時のときだけ呼びたい処理");  
        }
    }
    
    // ボタンをクリックしたらUniTaskのCancelを実行  
    void OnClick() => _cts?.Cancel();  
    

    例としてキャンセルボタンをクリックしたらUniTaskがキャンセルされるサンプルです。

    try節の中でUniTaskを実行し、catch節の中でキャンセル時の分岐を記述します。

    CancellationTokenSourceCancelを実行すると OperationCanceledException 例外がスローされます。この例外をもとにキャンセル後の処理を実行するというわけです。

    あえてもう一度言いますが、 UniTaskのキャンセルとは例外扱い だということを覚えておきましょう。

    今までtry-catchはエラーやNullReferenceException(ヌルポ)や、ミス、間違いといった例外をチェックするために使っていましたが、キャンセルも例外としてとらえる必要が出てきた ということです。

    オオバ
    オオバ
    コルーチン時代と大きく違いますね

    UniTaskを使いこなすために考え方を変えていきましょう。

    まとめ : UniTaskのキャンセルは例外処理とおぼえてasync/awaitを使いこなそう

    UniTaskのキャンセルについて解説しました。最後に記事の内容を簡単にまとめます。

    UniTaskのキャンセルは例外処理とおぼえてasync/awaitを使いこなそう

    ①UniTaskはGameObjectのOFFでは停止しない

    ②UniTaskのキャンセルにはCancellationTokenを使う

    ③UniTaskのキャンセルは例外処理である

    こんな感じです。

    UniTask(async/await)によって得られる絶大なメリットと引き換えに、コルーチンと比べると複雑になったキャンセル処理が発生しました。

    重要なのはキャンセルは例外であるという意識です。コルーチンに慣れ過ぎていると、async/awaitの感覚は掴みづらいかも知れません。

    しかし一度UniTaskに慣れると便利すぎて戻れないです。ぜひUniTaskを使った非同期処理に挑戦してもらえればと思います。

    改めて、UniTaskの基礎から学びたい方は次の記事を復習してみてください。

    アニメーションライブラリDOTweenとUniTaskの連携はこちらの記事で解説していますので、ぜひ参考にしてみてください。

    「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
    ぜひチャンネル登録をお願いします!

    最後まで読んでいただきありがとうございました!
    すばらしいUniTaskライフをお過ごしください。

    オススメ記事
    検証環境
    • Unity2020.3.15f2
    • UniTask v2.2.5
    参考サイト