こんにちは、Unityエンジニアのオオバです。
Unityをお使いのみなさまasync/await
便利ですよね。コルーチンと比べてコードがスッキリ しました。
今やUnityで非同期処理の実装は UniTask一択 といっても過言ではありません。
オオバも ゲーム開発の現場でコルーチンを使うことは一切なくなりました。 Unity初心者やプログラミング初心者に教えるときに少しだけコルーチンを紹介する程度です。
そもそもasync/await登場前の非同期処理は コルーチン でした。
もちろん今でもコルーチンは使えますし、古いタイトルの運用にコルーチンを使っている現場はあると思います。
しかし、コルーチンと比べて UniTaskのキャンセル処理が難しい という声をよく聞きます。
そうなんです。UniTaskのキャンセルはコルーチンより少し面倒くさいです。
そこで本記事では UniTaskのキャンセル処理について初心者向けにわかりやすく解説 します。
UniTaskにおいてキャンセル処理は鬼門 なので気合を入れて丁寧に説明していきますね!ぜひ、みなさんもついてきてください。
↓そもそもUniTaskを始めたことがない方は、まずはこちらの記事を参考にすることをオススメします。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
なぜUniTaskのキャンセル処理が必要なのか?
ゲーム開発をしていく上で処理のキャンセルは必須です。キャンセル処理を実装するポイントは大きく2点です。
- 演出のスキップ
- エラー発生時に後続処理をスキップ
1.演出のスキップ
演出のスキップは必ずと言ってよいほど実装します。具体的にはガチャアニメーションなどのスキップです。ガチャアニメーション中、ユーザーのタップでスキップすると思います。
ユーザーが画面をタップ、またはスキップボタンを押した瞬間、特定のポイントにジャンプしますよね。具体的には演出後に表示される結果画面です。つまり、結果画面までのアニメーション処理をスキップする必要があるのです。
2.エラー発生時に後続処理のスキップ
エラー発生時に後続処理のスキップは アプリを安定稼働させるうえで必須事項 です。
例えば、画面を表示させるとき通信エラーが起きたとします。その後に行う予定だった「画像のロード」や「インスタンスの生成」をキャンセルして、エラーダイアログを表示させる必要があるといった感じです。
つまりUniTaskのキャンセルは開発する上で知っておかねば、思わぬ不具合をもたらし、最悪アプリがクラッシュします。
この記事では、UniTaskのキャンセルを丁寧に解説しているので、ぜひ最後まで読んでマスターしてみてください。
結論UniTaskのキャンセルにはCancellationTokenを使う
結論から話すと、UniTaskのキャンセルには CancellationToken を使います。
詳しい解説は後ほどしますが、ここでは簡潔にまとめます。
- UniTaskはGameObjectのOFFでは止まらない(キャンセルできない)
- UniTaskはCancellationTokenで停止する
- 全てのUniTaskにCancellationTokenは引き渡したほうがいい
重要なことなので何度も言いますが、コルーチンと違ってUniTaskはGameObjectのOFFでは止まりません。
次の章からは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は止まりません。
このように 1秒後のログはしっかり出力 されます。つまりUniTaskとGameObjectは分離して考える必要があるということです。
↓UniTaskの文法がよくわからないという方は、次の記事を参考にしてみてください。
UniTaskはCancellationTokenで停止する
結論から話すと、具体的なUniTaskのキャンセル方法は 「CancellationTokenSourceのCancelメソッドを呼ぶ」 です。
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
CancellationToken
とCancellationTokenSource
はUniTaskのキャンセルに必要な要素です。
var cts = new CancellationTokenSource();
CancellationToken token = cts.token;
↑コードのとおりCancellationToken
は、CancellationTokenSource
から生成します。
cts.Cancel();
↑UniTaskをキャンセルするときは、CancellationTokenSource
の Cancelメソッド を呼びます。
↓例えば UniTask.Delay()
は以下のように第2引数にCancellationTokenをセットすることでキャンセル処理に対応可能です。
await UniTask.Delay(1000, token);
つまり、CancellationTokenをセットしないとUniTaskのキャンセル処理は実装できないということです。
UniTask(async/await)の世界を渡り歩くためには 「CancellationToken」と「CancellationTokenSource」の理解は必須です。
あいまいな状態にしておくと 思わぬエラー、メモリリークなど を引き起こします。ぜひこの機会にマスターしておきましょう。
複数のUniTaskを同時にキャンセルさせる
CancellationToken
とCancellationTokenSource
の理解を深めるために複数の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節
の中でキャンセル時の分岐を記述します。
CancellationTokenSource
のCancel
を実行すると OperationCanceledException 例外がスローされます。この例外をもとにキャンセル後の処理を実行するというわけです。
あえてもう一度言いますが、 UniTaskのキャンセルとは例外扱い だということを覚えておきましょう。
今までtry-catchはエラーやNullReferenceException(ヌルポ)や、ミス、間違いといった例外をチェックするために使っていましたが、キャンセルも例外としてとらえる必要が出てきた ということです。
UniTaskを使いこなすために考え方を変えていきましょう。
まとめ : UniTaskのキャンセルは例外処理とおぼえてasync/awaitを使いこなそう
UniTaskのキャンセルについて解説しました。最後に記事の内容を簡単にまとめます。
①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