【UniTask】キャンセル時に例外を発生させずにハンドリングできるSuppressCancellationThrowの使い方

UniTaskにおいてキャンセル時に例外を発生させない SuppressCancellationThrow の使い方をまとめました。

SuppressCancellationThrowの使い方

UniTaskでは、引数に渡したCancellationTokenによりキャンセルが行われると、OperationCanceledExceptionが発生します。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    private async void Start()
    {
        try
        {
            await UniTask.Delay(TimeSpan.FromSeconds(10), cancellationToken: _cancellationTokenSource.Token);
        }
        catch (OperationCanceledException)
        {
            Debug.Log("キャンセル発生");
            throw;
        }
    }

    private void Update()
    {
        // スペースキーでキャンセル
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _cancellationTokenSource.Cancel();
        }
    }
}

これに対して、以下のようにSuppressCancellationThrowを使うと、キャンセル時に例外をスローせず戻り値からキャンセル状態を取得できるようになります。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    private async void Start()
    {
        var isCanceled = await UniTask
            .Delay(TimeSpan.FromSeconds(10), cancellationToken: _cancellationTokenSource.Token)
            .SuppressCancellationThrow();
        
        // 戻り値からキャンセル状態を取得できる
        Debug.Log(isCanceled);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _cancellationTokenSource.Cancel();
        }
    }
}

戻り値ありの場合

戻り値ありの非同期メソッドの場合にSuppressCancellationThrowを使うと、以下のようにキャンセル状態と結果の両方を取得できます。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    private async void Start()
    {
        var result = await UniTask
            .Delay(TimeSpan.FromSeconds(10), cancellationToken: _cancellationTokenSource.Token)
            .ContinueWith(() => 123)
            .SuppressCancellationThrow();

        if (!result.IsCanceled)
            Debug.Log(result.Result);
        else
            Debug.Log("Canceled");
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) _cancellationTokenSource.Cancel();
    }
}

使いどころ

使いどころとしては、例えば「ロードがキャンセルされなかった時のみ画像を設定する、キャンセルされたら何もしない」というようなケースが考えられます。

using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;

public sealed class Example : MonoBehaviour
{
    [SerializeField] private RawImage image;

    public async void SetImageAsync(string resourceKey, CancellationToken cancellationToken)
    {
        var result = await Addressables.LoadAssetAsync<Texture2D>(resourceKey)
            .ToUniTask(cancellationToken: cancellationToken)
            .SuppressCancellationThrow();

        if (result.IsCanceled)
            return;

        image.texture = result.Result;
    }
}

ただし一般的には同期メソッドはキャンセル時に例外をスローすることが期待されるため、適用するスコープには注意が必要です。