UniTaskのキャンセル処理についてまとめました。
Unity2020.1.10
UniTask 2.0.37
非同期メソッドのキャンセル方法
まず非同期メソッドのキャンセル方法についてまとめます。
Taskの場合
UniTaskについて書く前にTaskについてまとめます。
Taskをキャンセルにするには、以下のようにCancellationTokenを非同期メソッドに渡します。
using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.UI; public class Example : MonoBehaviour { [SerializeField] private Button _cancelButton; private async void Start() { // CancellationTokenSourceを生成 var cts = new CancellationTokenSource(); _cancelButton.onClick.AddListener(() => { cts.Cancel(); }); // CancellationTokenSource.Tokenを非同期メソッドに渡す await ExampleAsync(cts.Token); } private async Task ExampleAsync(CancellationToken cancellationToken = default) { // この時点でキャンセルされていたらOperationCanceledExceptionを投げるメソッド cancellationToken.ThrowIfCancellationRequested(); for (var i = 0; i < 5; i++) { // TaskのファクトリメソッドにcancellationTokenを渡せる await Task.Run(() => Thread.Sleep(1000), cancellationToken); } } }
キャンセルすると例外がスローされるのでそれをハンドリングします。
UniTaskの場合
UniTaskの場合も基本的にはTaskと同じ方法でキャンセル処理を行います。
using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UI; public class Example : MonoBehaviour { [SerializeField] private Button _cancelButton; private async void Start() { // CancellationTokenSourceを生成 var cts = new CancellationTokenSource(); _cancelButton.onClick.AddListener(() => { cts.Cancel(); }); // CancellationTokenSource.Tokenを非同期メソッドに渡す await ExampleAsync(cts.Token)); } private async UniTask ExampleAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); for (var i = 0; i < 5; i++) { await Task.Run(() => Thread.Sleep(1000), cancellationToken); } } }
UnityのAsyncOperation系
UniTaskではUnityのAsyncOperation系の構造体にGetAwaiter()を実装していますが、
これらについては引数にCancellationTokenを渡すことができません。
これらには以下のようにWithCancellation()という拡張メソッドが定義されているので、これを使ってCancellationTokenを渡します。
using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; public class Example : MonoBehaviour { [SerializeField] private Button _cancelButton; private async void Start() { // CancellationTokenSourceを生成 var cts = new CancellationTokenSource(); _cancelButton.onClick.AddListener(() => { cts.Cancel(); }); // UnityのAsyncOperationにAwaiterを定義してある系のものにはWithCancellationで渡す await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token); } }
GameObjectのライフサイクルに紐づいたCancellationToken
UniTaskではGameObjectのライフサイクルと紐づいたCancellationTokenを取得することができます。
これを非同期メソッドに渡すと、GameObjectが破棄されたらキャンセルされる処理が実装できます。
using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { // GameObjectがDestroyされたときにキャンセルされるCancellationTokenを生成する await ExampleAsync(this.GetCancellationTokenOnDestroy()); } private async UniTask ExampleAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); for (var i = 0; i < 5; i++) { await Task.Run(() => Thread.Sleep(1000), cancellationToken); } } }
キャンセルのハンドリング方法
次に非同期メソッドキャンセルのハンドリング方法についてまとめます。
基本的にはtry-catch
非同期メソッドがキャンセルされたときにはOperationCanceledException
という例外がスローされます。
TaskでもUniTaskでも基本的にはこの例外をtry-catchしてハンドリングします。
using System; using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { try { await ExampleAsync(this.GetCancellationTokenOnDestroy()); } catch (OperationCanceledException e) { // OperationCanceledExceptionをキャッチしてキャンセルをハンドリング Debug.Log("キャンセルされた"); throw; } } private async UniTask ExampleAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); for (var i = 0; i < 5; i++) { await Task.Run(() => Thread.Sleep(1000), cancellationToken); } } }
SuppressCancellationThrowでキャンセル状態を戻り値として得る
UniTaskの場合には以下のようにUniTask.SuppressCancellationThrow()
を使うことでキャンセル状態を戻り値として受け取ることができます。
using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { // SuppressCancellationThrowによりキャンセル状態が戻り値として返される var canceled = await ExampleAsync(this.GetCancellationTokenOnDestroy()).SuppressCancellationThrow(); if (canceled) { Debug.Log("キャンセルされた"); } } private async UniTask ExampleAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); for (var i = 0; i < 5; i++) { await Task.Run(() => Thread.Sleep(1000), cancellationToken); } } }