コガネブログ

平日更新を目標に Unity や C#、Visual Studio、ReSharper などのゲーム開発アレコレを書いていきます

【Unity】UniTask.WhenAll に CancellationToken を渡したい場合

概要

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

public class Example : MonoBehaviour
{
    private CancellationTokenSource m_cancellationTokenSource;

    private void Update()
    {
        if ( Input.GetKeyDown( KeyCode.Z ) )
        {
            RunAsync().Forget();
        }
        else if ( Input.GetKeyDown( KeyCode.X ) )
        {
            Debug.Log( "キャンセル" );

            m_cancellationTokenSource?.Cancel();
            m_cancellationTokenSource = null;
        }
    }

    private async UniTaskVoid RunAsync()
    {
        m_cancellationTokenSource = new();

        Debug.Log( "開始" );

        var cancellationToken = m_cancellationTokenSource.Token;

        await UniTask
                .WhenAll( Impl( 1 ), Impl( 2 ), Impl( 3 ) )
                .AttachExternalCancellation( cancellationToken )
            ;

        Debug.Log( "完了" );

        async UniTask Impl( double seconds )
        {
            Debug.Log( $"開始:{seconds}" );
            await UniTask.Delay( TimeSpan.FromSeconds( seconds ) );
            Debug.Log( $"完了:{seconds}" );
        }
    }
}

AttachExternalCancellation だと UniTask.WhenAll から先には進まなくなるが
UniTask.WhenAll に渡した個々の UniTask は止まらない

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

public class Example : MonoBehaviour
{
    private CancellationTokenSource m_cancellationTokenSource;

    private void Update()
    {
        if ( Input.GetKeyDown( KeyCode.Z ) )
        {
            RunAsync().Forget();
        }
        else if ( Input.GetKeyDown( KeyCode.X ) )
        {
            Debug.Log( "キャンセル" );

            m_cancellationTokenSource?.Cancel();
            m_cancellationTokenSource = null;
        }
    }

    private async UniTaskVoid RunAsync()
    {
        m_cancellationTokenSource = new();

        Debug.Log( "開始" );

        var cancellationToken = m_cancellationTokenSource.Token;

        await UniTask.WhenAll
        (
            Impl( 1, cancellationToken ),
            Impl( 2, cancellationToken ),
            Impl( 3, cancellationToken )
        );

        Debug.Log( "完了" );

        async UniTask Impl( double seconds, CancellationToken cancellationToken )
        {
            Debug.Log( $"開始:{seconds}" );

            await UniTask.Delay
            (
                TimeSpan.FromSeconds( seconds ),
                cancellationToken: cancellationToken
            );

            Debug.Log( $"完了:{seconds}" );
        }
    }
}

AttachExternalCancellation を使わずに
UniTask.WhenAll に渡した個々の UniTask に cancellationToken を渡すと
UniTask.WhenAll も個々の UniTask も止まる