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

みなさまasync/await便利ですよね。
Unityで非同期は UniTask一択 なんですが、
キャンセル処理が難しいという声をよく聞きます。

本記事ではUniTaskのキャンセル処理について
初心者向けに解説します。

キャンセル処理は鬼門なので気合を入れて
丁寧に説明していきます。

この記事の内容

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

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_0

いつもどおり結論から解説はしますが、
いきなり CancellationToken と言われても。。。
という感じかもしれません。

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

UniTaskはGameObjectのOFFでは止まらない
こちらは前回解説した内容です。
そう、 GameObjectがDestroyしても
UniTaskは動き続けます。

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

UniTaskのキャンセル処理についての詳細を解説していきます。

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

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_1

コルーチンからUniTaskに切り替えた時、
最初につまづく点は GameObjectに
UniTaskがひも付かない点
です。

💻ソースコード : UniTaskがGameObjectのOFFで停止しないサンプル
async void Start()  
{
    TestUniTask();  
    gameObject.SetActive(false);  
}

async UniTask TestUniTask()  
{
    await UniTask.Delay(1000);  
    Debug.Log("Unitask完了");  
}

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

1秒後のログがしっかり出力されます。

つまりUniTaskとGameObjectは分離して考えましょう。

まずはここからです。

UniTaskはCancellationTokenで停止する

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_2

UniTaskのキャンセルとは、
非同期処理をするUniTaskの停止 を指します。

UniTaskを停止させるサンプルコードはこちらです。

UniTaskのキャンセルサンプル
async Start()  
{
    var cts = new CancellationTokenSource();  
    CancellationToken token = cts.token;  
    Wait5SecUniTask(token);  
    await UniTask.Delay(TimeSpan.FromSeconds(1));  
    // UniTaskのキャンセル実行  
    cts.Cancel();  
}

async UniTask Wait5SecUniTask(CancellationToken token)  
{
    await UniTask.Delay(TimeSpan.FromSeconds(5), token);  
    Debug.Log("Complete!");  
}

CancellationTokenSourceCancelメソッドで停止します。

登場したこの2つについて解説していきます。
ここはとても重要なところです。

CancellationTokenSourceから生まれるCancellationToken

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

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

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

cts.Cancel();  

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

例えば UniTask.Delay()はキャンセルに対応しています。

await UniTask.Delay(1000, token);  

第2引数にCancellationTokenを代入すれば
キャンセルすることができます。

UniTask(async/await)の世界を渡り歩くためには
CancellationToken、
CancellationTokenSourceの理解は必須
です。

あいまいな状態にしておくと
思わぬエラー、メモリリークなどを引き起こします。

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

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_3

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

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

複数のTaskの同時キャンセル
async void Start()  
{
    var cts = new CancellationTokenSource();  
    CancellationToken token = cts.token;  

    TestUniTask(1, token);  
    TestUniTask(2, token);  
    TestUniTask(3, token);  
    TestUniTask(4, token);  

    await UniTask.Delay(TimeSpan.FromSeconds(1));  
    // ctsに紐づくTaskがキャンセルされる  
    cts.Cancel();  
}

async UniTask TestUniTask(int id, CancellationToken token)  
{
    await UniTask.Delay(TimeSpan.FromSeconds(5), token);  
    Debug.Log($"UniTask : {id} 完了");  
}

なんとなくわかってきましたでしょうか。

オオバ
オオバ
ややこしいですね。

キャンセル処理は例外あつかい

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_4

UniTaskの停止は理解できたと思います。
ではキャンセル後の分岐はどうすればよいか。

結論、try-catchを使います。

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

💻ソースコード : UniTaskのキャンセル分岐サンプル
CancellationTokenSource _cts;  

async void Start()  
{
    _cts = new CancellationTokenSource();  
    try {  
        await TestUniTask(_cts.token);  
        Debug.Log("UniTask完了");  
    }
    catch (OperationCanceledException e){  
        // キャンセル時に呼ばれる例外  
        Debug.Log("Taskキャンセル時の処理");  
    }
}

// ボタンをクリックしたらCancel実行  
void OnClick() => _cts?.Cancel();  

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

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

CancellationTokenSourceCancelを実行すると
OperationCanceledException という
例外がスローされます。

この例外をもとにキャンセル後の処理を実行するというわけです。

つまりUniTaskのキャンセルとは例外扱いだということです。

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

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

マインドを変える必要があります。

まとめ : async/awaitを使う上でUniTaskのキャンセルは例外処理です

【超基礎】async/awaitを使う上でUniTaskのキャンセルは例外処理である件_5

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

こんな感じです。

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

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

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

このブログの内容が 分からない
分かりづらい 、などありましたら、
お気軽にTwitterでDMをください。

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

期間限定 最大95%オフセール
効率UPメガバンドル開催中!最大95%オフ!!!
期間 : 11月1日午後15時59分まで
オススメ記事
検証環境
参考サイト