UniTaskのキャンセルの基本
UniTaskは、完了までに時間がかかる非同期処理です。非同期処理を実行する際、ある条件でその処理を完了前に取りやめたいことが良くあります。例えば、サーバーからデータを取得しようとしたが、取得に時間がかかりすぎている場合に取得を中止し、通信に失敗した旨をユーザーに伝えたい場合です。
今回は、UniTaskの実行中にその処理を中止する方法について学んでいきます。
CancellationTokenオブジェクト
UniTaskは、CancellationToken
オブジェクトを使用してキャンセルすることができます。例として、次のコードを作成し、適当なオブジェクトにアタッチして実行しましょう。
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
private CancellationTokenSource cancellationTokenSource = new();
private void Start()
{
CancellationToken cancellationToken = cancellationTokenSource.Token;
_ = StartTimerAsync("Timer A", 3, cancellationToken);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Spacekey pushed!");
cancellationTokenSource.Cancel();
}
}
private void OnDestroy()
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
}
C#実行してから何もせずにいると、指定した秒数でタイマーが終了します。
実行してから、指定秒数が経過するまでにスペースキーを押すとタイマーが途中で止まります。
UniTaskがキャンセルされる仕組みについてみていきます。
まず、StartTimerAsync
メソッドはパラメータとしてCancellationToken
オブジェクトを要求します。CancellationToken
オブジェクトはUniTaskのキャンセルを引き起こすオブジェクトであり、様々な方法で作成することができます。ここでは、CancellationTokenSource
のインスタンスを作成し、そこからCancellationToken
オブジェクトを取得しています。
private CancellationTokenSource cancellationTokenSource = new();
private void Start()
{
CancellationToken cancellationToken = cancellationTokenSource.Token;
_ = StartTimerAsync("Timer A", 3, cancellationToken);
}
C#StartTimerAsync
メソッドがパラメータとして受け取ったCancellationToken
オブジェクトは、UniTask.Delay
メソッドに渡されます。UniTask.Delay
メソッドは、渡されたCancellationToken
オブジェクトがキャンセルされたときにOperationCanceledException
例外を送出し、処理を中断させす。
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
C#CancellationTokenSource
より取得したCancellationToken
オブジェクトは、取得元のCancellationTokenSource
インスタンスのCancel
メソッドが呼び出されたときに、キャンセルされます。ここでは、スペースキーを押したときに、CancellationTokenSource
のインスタンスがキャンセルされ、それによりCancellationToken
オブジェクトがキャンセルされるようになっています。
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Spacekey pushed!");
cancellationTokenSource.Cancel();
}
}
C#まとめると、次の手順でStartTimerAsync
メソッドがキャンセルされます。
- スペースキーが押される
CancellationTokenSource
のCancel
メソッドが呼ばれるStartTimerAsync
メソッドに渡されたCancellationToken
オブジェクトがキャンセルされるUniTask.Delay
メソッドから、OperationCanceledException
例外が送出される- 例外により、
StartTimerAsync
メソッドの処理が中断される
なお、CancellationTokenSource
はIDisposable
インターフェースを実装しているので、プログラム終了時に必ずDispose
メソッドを呼び出すようにします。また、Dispose
を呼び出す前、Cancel
を呼び出してキャンセルもしておきます。これは、そのCancellationTokenSource
由来のCancellationToken
が渡されたUniTaskの処理がプログラムの終わりで終了することを保証するためです。これについては、後に詳しく学びます。
private void OnDestroy()
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
C#キャンセル時の処理を定義する
UniTaskのキャンセルは、OperationCanceledException
例外が送出されることで引き起こされます。そのため、try-catch
構文により、UniTaskがキャンセルされたときの動作を記述することができます。
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
}
C#try-catch-finally
を使用すれば、キャンセルされたかされていないかに関わらず、必ず実行したい処理を記述することもできます。
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
finally
{
Debug.Log("From finally...");
}
}
C#OperationCanceledException例外を再度スローするか
UniTaskがキャンセルされた際に送出されるOperationCanceledException
例外は、例外の一つなので、catch
で例外をキャッチした後に、throw;
によって再度例外をするかどうかによって、処理の挙動が変わります。
例えば、次の二つのコードはキャンセル時に異なる結果を示します。
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
Debug.Log("End of method");
}
C#private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
}
Debug.Log("End of method");
}
C#UniTaskのキャンセル時、1つ目のコードではメッセージ”End of method”が表示されませんが、2つ目のコードでは表示されます。
これは、1つ目のコードでは例外がcatch
文の中で再度送出されたことによりtry-catch
文以降のメソッドの処理が中断されるのに対し、2つ目のコードではcatch
文の中で例外を握りつぶしているために、try-catch
文以降の処理が例外の影響を受けないためです。
OperationCanceledExeption
例外をcatch
文でキャッチすることでUniTaskがキャンセルされた場合の処理を記述する場合は、そのキャンセルをなかったことにしたいのか、他の場所へそのキャンセルを伝播させたいのかを場面に合わせて考える必要があります。
キャンセルの判定を自身で定義する
前の例でのUniTaskのキャンセルは、CancellationToken
がキャンセルされ、それが渡されていたUniTask.Delay
からOperationCanceledException
例外が送出することで引き起こされていました。
基本的には、そのあたりの処理はUniTask.Delay
のようなすでに用意されているメソッドに任せればよいですが、自分自身でCancellationToken
オブジェクトがキャンセルされているかを確認し、例外を送出することもできます。
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
if (cancellationToken.IsCancellationRequested)
{
throw new OperationCanceledException();
}
}
Debug.Log($"{name} finished!");
}
C#上の例では、CancellationToken
がキャンセルされているかをcancellationToken.IsCancellationRequested
により確認し、キャンセルされている場合に例外を送出しています。
これは、次のような書き方もできます。
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
cancellationToken.ThrowIfCancellationRequested();
}
Debug.Log($"{name} finished!");
}
C#ThrowIfCancellationRequested
は、CancellationToken
のキャンセルの状態を判定し、キャンセルされていればOperationCanceledException
例外を送出します。
なお、このようなコードはUniTask.Delay
に直接CancellationToken
を渡した場合とは少し挙動が異なることに注意してください。UniTask.Delay
に直接CancellationToken
を渡した場合はキャンセル時に処理が即座に中断されますが、上の例では、キャンセル時には必ずUniTask.Delay
の処理が完了してから例外が送出され、後続の処理が中断されることになります(UniTask.Dealy
の指定秒数を長めに設定すると、違いが分かりやすいです)。
CancellationTokenの作り方
前のセクションの例では、UniTaskのキャンセルに必要なCancellationToken
オブジェクトを、CancellationTokenSource
のインスタンスから取得していました。
実際には、CancellationTokenSource
だけでなく、いくつかのCancellationToken
を取得する方法があります。ここからは、それらについて学習します。
CancellationTokenSourceから取得する
CancellationToken
を取得する一番基本的な方法が、すでに例で確認した通り、CancellationTokenSource
のインスタンスから取得する方法です。
実は、CancellationToken
もCancellationTokenSource
もC#標準で用意されているものであり、C#標準のTask
を使用した非同期処理のキャンセルに使われます。System.Threading
名前空間で定義されています。
using System.Threading;
// CancellationTokenSourceのインスタンスの作成
CancellationTokenSource cancellationTokenSource = new();
// CancellationTokenオブジェクトの取得
CancellationToken cancellationToken = cancellationTokenSource.Token;
// 好きなタイミングでCancelationTokenをキャンセル
cancellationTokenSource.Cancel();
// 使い終わったら必ずDisposeする
cancellationTokenSource.Dispose();
C#UniTaskから変換する
CancellationToken
を、UniTaskから変換して取得することができます。このCancellationToken
がキャンセルされるのは、変換元のUniTaskが終了したときです。
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
private void Start()
{
CancellationToken tokenFromUniTask = StartTimerAsync("Timer A", 2, default).ToCancellationToken();
_ = StartTimerAsync("Timer B", 5, tokenFromUniTask);
}
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f));
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
}
}
}
C#この例では、StartTimerAsync
メソッドを、秒数をそれぞれ2秒(Timer A)と5秒(Timer B)で指定して、2回呼び出しています。Timer AのUniTaskはToUniTask
メソッドによってCancellationToken
オブジェクトに変換され、Timer BのUniTaskに渡されています。
プログラムを実行すると、指定された秒数はTimer Aの方が短いので、Timer AのUniTaskの方が先に終了します。そして、Timer AのUniTaskが終了すると、そこから変換されたCancellationToken
がキャンセルされ、それが渡されているTimer BのUniTaskがキャンセルされます。
このように、ToCancellationToken
メソッドでUniTaskを変換することで、変換元のUniTaskが終了したときにキャンセルされるようなCancellationToken
を作ることができます。
GameObjectから生成する
CancellationToken
はGetCancellationTokenOnDestroy
メソッドによってGameObjectから取得することができ、この時、CancellationToken
は取得元のGameObjectが破棄されるとき、キャンセルされます。
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
private async UniTaskVoid Start()
{
GameObject sourceObject = new();
CancellationToken tokenFromObject = sourceObject.GetCancellationTokenOnDestroy();
_ = StartTimerAsync("Timer A", 2, tokenFromObject);
await UniTask.Delay(TimeSpan.FromSeconds(1));
Destroy(sourceObject);
}
private async UniTask StartTimerAsync(string name, int seconds, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (seconds > 0)
{
Debug.Log($"{name}: {seconds} sec.");
seconds--;
await UniTask.Delay(TimeSpan.FromSeconds(1.0f), cancellationToken: cancellationToken);
}
Debug.Log($"{name} finished!");
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
}
}
}
C#この例では、まず1つのGameObject
を作成し、そこからGetCancellationTokenOnDestroy
メソッドによりCancellationToken
オブジェクトを得ています。そして、得られたオブジェクトを、StartTimerAsync
メソッドに渡しています。メソッド呼び出し後は、1秒待機した後にCancellationToken
の取得元であるGameObject
を破棄するようにしています。
Destroy
によってGameObjecy
が破棄されると、そのオブジェクトに対するGetCancellationTokenOnDestroy
メソッド呼び出しによって得られたCancellationToken
はキャンセルされます。そのため、上の例ではオブジェクトの破棄と同時にUniTaskがキャンセルされています。
この方法は、例えば終了することがないUniTaskに渡すCancellationToken
の作成に向いています。後に学習しますが、すべてのUniTaskは、それが不必要になったときに必ずキャンセルされることを保証しなければなりません。そうしなければ、リソースリークを引き起こすかもしれないからです。
UniTaskが不必要になるタイミングでよくあるのは、現在のシーンから新たなシーンへと移行するときです。シーンの遷移時、そのシーンでのみ必要なUniTaskは、新たなシーンではすべて不必要になるためキャンセルされる必要があります。シーン遷移時、古いシーンにあるオブジェクトはすべて破棄されるので、GetCancellationTokenOnDestroy
によって取得したCancellationToken
をUniTaskに渡して実行すると、そのUniTaskはシーン遷移時、自動的にキャンセルされることになります。
CancellationToken tokenFromObject = this.GetCancellationTokenOnDestroy();
// 終了しないUniTask
_ = NeverFinishAsync(tokenFromObject);
// ここでシーン遷移が起こり、オブジェクトが破棄されトークンもキャンセルされる
SceneManager.LoadScene("Next Scene");
C#CancellationTokenSource
とOnDestroy
を組み合わせて同様のことができますが、コードは冗長になります。
private CancellationTokenSource cancellationTokenSource = new();
private void Start()
{
CancellationToken cancellationToken = cancellationTokenSource.Token;
_ = NeverFinishAsync(cancellationToken);
}
private void OnDestroy()
{
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
}
C#UniTaskの終了を保証する
UniTaskにCancellationToken
を渡すことで、UniTaskの実行をキャンセルできることを学習しました。ここで、CancellationToken
はUniTaskにキャンセルのために渡すオプションの引数と考えるよりは、特別な理由がない限りは必ず渡す引数であると考えた方が良いです。なぜなら、すべてのUniTaskは、それが不必要になったときに、キャンセルされたか正常に完了したかを問わず処理が終了していることを保証しなければならないからです。
不必要になったUniTaskを必ず終了させないといけない理由を理解するため、1つの例を見てみます。
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SampleManager : MonoBehaviour
{
private static int count = 0;
private async UniTaskVoid Start()
{
count++;
_ = RepeatMessageAsync($"Message from scene {count}");
await UniTask.Delay(TimeSpan.FromSeconds(1));
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
private async UniTask RepeatMessageAsync(string message)
{
while (true)
{
Debug.Log(message);
await UniTask.Delay(TimeSpan.FromSeconds(1));
}
}
}
C#RepeatMessageAsync
メソッドは、指定されたメッセージを1秒ごとに、永遠に表示し続けるUniTaskです。Start
メソッドでは、このメソッドを呼び出し、1秒後に現在のシーンを再度読み込むという処理を行っています。なお、何回目に読み込まれたシーンからメソッドが呼び出されているのかを判別するため、静的フィールド変数のcount
を定義しています。
これを実行すると、以下のような結果になります。
結果からわかる通り、シーンの遷移が発生したとしても、UniTaskの処理は終わりません。シーンを新たに読み込むごとに、実行中であるUniTaskの数は際限なく増えていきます。ここでは明らかなリソースリークが発生しており、そのうちメモリ不足によりアプリケーションはクラッシュするかもしれません。
重要なのは、UniTaskと、それを呼び出したGameObjectの寿命の間には何にも関係がないということです。新たなシーンが読み込まれ、UniTaskを呼び出したGameObjectが破棄されたとしても、UniTaskは残り続けます。そのため、UniTaskが不必要になった時点で、リソースリークを避けるため、処理が完了していないUniTaskはキャンセルし、その処理が終了することを保証する必要があるのです。
上のコードを正しく書き直すと、次のようになります。
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SampleManager : MonoBehaviour
{
private static int count = 0;
private async UniTaskVoid Start()
{
count++;
_ = RepeatMessageAsync($"Message from scene {count}", this.GetCancellationTokenOnDestroy());
await UniTask.Delay(TimeSpan.FromSeconds(1));
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
private async UniTask RepeatMessageAsync(string message, CancellationToken cancellationToken)
{
while (true)
{
Debug.Log(message);
await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cancellationToken);
}
}
}
C#このコードでは、RepeatMessageAsync
がCancellationToken
によってキャンセルできるようになっており、また、その呼び出し時にはGetCancellationTokenOnDestroy
によって生成したCancellationToken
を渡しています。そのため、新しいシーンの読み込み時にはSampleManager
オブジェクトが破棄され、それに結び付いたCancellationToken
がキャンセルされ、古いシーンで呼び出されたUniTaskが停止することを保証することができます。結果を見ても、シーンが切り替わった時点で、古いシーンからのメッセージの表示は停止していることがわかります。
繰り返しますが、すべてのUniTaskについて、それが不必要になったときにその処理が停止していることを保証しなければなりません。永遠に終了しないUniTaskが残り続けると、そのうち実行中のUniTaskの数が膨大になってアプリケーションのクラッシュを引き起こしたり、あるいは意図せずに残っているUniTaskにより、意図しない動作が引き起こされたりするかもしれないからです。呼び出すUniTaskが、そのシーン内ではキャンセルされず、永遠に動作し続けるものだとしても、必ずCancellationToken
を渡し、そのシーンの終了時にはキャンセルされるようにしなければいけません。
UniTask Tracker
すべてのUniTaskが、処理を完了もしくは適切にキャンセルされることを保証しようとしても、意図せずに残り続けるUniTaskが生まれてしまうことは良くあります。そこで、UniTask Trackerによって、意図せずに動作が続いているUniTaskがないかどうかをチェックすることができます。
上部メニューのWindow/UniTaskTrackerからUniTask Trackerを開き、Enable AutoReloadとEnable Trackingを有効にしたうえで、悪いコードと良いコードのそれぞれを実行してみます。
悪いコードの実行時にはPending状態のUniTaskが続々と増えていき、良いコードの実行時には、新たなPending状態のUniTaskが出現すると同時に、古いUniTaskがCanceled状態になって停止していることがわかります。
UniTaskを活用して開発を進める際には、定期的にUniTask Trackerにより、適切に停止していないUnitaskがないかどうかをチェックすると良いでしょう。
演習
CanellationTokenSourceを使用したUniTaskのキャンセル
以下のプログラムを変更して、スペースキーを押したときにTask AとTask Bが、エンターキーを押したときにTask Cがそれぞれキャンセルされるようにしましょう。CancellationToken
の生成には、CancellationTokenSource
を使用します。
ちなみに、StartTaskAsync
メソッドはキャンセルされるまでは終了しないUniTaskです。UniTask.DelayFrame
は、指定したフレーム分だけ遅延させるUniTaskで、ライブラリのUniTaskから提供されています。
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
private void Start()
{
_ = StartTaskAsync("Task A", default);
_ = StartTaskAsync("Task B", default);
_ = StartTaskAsync("Task C", default);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Spacekey pushed!");
}
if (Input.GetKeyDown(KeyCode.Return))
{
Debug.Log("Returnkey pushed!");
}
}
private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (true)
{
await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
}
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
}
}
C#UniTaskをCancellationTokenに変換する
前問のプログラムを変更し、CancellationTokenSource
を使わず、UniTaskから変換して得られたCancellationToken
を使用してUniTaskをキャンセルできるようにしましょう。つまり、前問のプログラムにおけるUpdate
メソッドの中で行われていたキーの押下判定の処理をUniTaskで置き換え、そこからCancellationToken
を得ます。
解答例
CanellationTokenSourceを使用したUniTaskのキャンセル
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
CancellationTokenSource spacekeyTokenSource = new();
CancellationTokenSource returnkeyTokenSource = new();
private void Start()
{
_ = StartTaskAsync("Task A", spacekeyTokenSource.Token);
_ = StartTaskAsync("Task B", spacekeyTokenSource.Token);
_ = StartTaskAsync("Task C", returnkeyTokenSource.Token);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Spacekey pushed!");
spacekeyTokenSource.Cancel();
}
if (Input.GetKeyDown(KeyCode.Return))
{
Debug.Log("Returnkey pushed!");
returnkeyTokenSource.Cancel();
}
}
private void OnDestroy()
{
spacekeyTokenSource?.Cancel();
spacekeyTokenSource?.Dispose();
returnkeyTokenSource?.Cancel();
returnkeyTokenSource?.Dispose();
}
private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (true)
{
await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
}
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
}
}
C#CancellationTokenSource
のキャンセル(不要になった時点でUniTaskの停止を保証するため)と、Dispose(CancellationTokenSource
はIDisposable
インターフェースを実装するため)を忘れないようにしましょう。
UniTaskをCancellationTokenに変換する
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
public class SampleManager : MonoBehaviour
{
private void Start()
{
var spacekeyToken = WaitUntilSpacekeyPushed(this.GetCancellationTokenOnDestroy()).ToCancellationToken();
var returnkeyToken = WaitUntilReturnkeyPushed(this.GetCancellationTokenOnDestroy()).ToCancellationToken();
_ = StartTaskAsync("Task A", spacekeyToken);
_ = StartTaskAsync("Task B", spacekeyToken);
_ = StartTaskAsync("Task C", returnkeyToken);
}
private async UniTask WaitUntilSpacekeyPushed(CancellationToken cancellationToken)
{
while (!Input.GetKeyDown(KeyCode.Space))
{
await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
}
}
private async UniTask WaitUntilReturnkeyPushed(CancellationToken cancellationToken)
{
while (!Input.GetKeyDown(KeyCode.Return))
{
await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
}
}
private async UniTask StartTaskAsync(string name, CancellationToken cancellationToken)
{
try
{
Debug.Log($"{name} started...");
while (true)
{
await UniTask.DelayFrame(1, cancellationToken: cancellationToken);
}
}
catch (OperationCanceledException)
{
Debug.Log($"{name} canceled!");
throw;
}
}
}
C#CancellationToken
に変換するUniTaskを呼び出す際、やはりGetCancellationTokenOnDestroy
によって、不要になった時点で二つのUniTaskの処理が停止することを保証します。
コメント