본문 바로가기
- Unity/let us all UNITE !

[Unity] UniTask 의 토큰 Token (= CancellationToken)

by david_동근 2025. 10. 7.

UniTask 에서 Token(CancellationToken) 은 쉽게 말하면, "멈춰!" 토큰입니다.

고대 한국에서 사용하던 시내 버스 토큰입니다.

 


 

Token (= CancellationToken)

토큰은 비동기 작업이 더 이상 의미가 없을 때, 바로 멈출 때가 되었다고 알려주는 플래그 같은 것입니다.

프로그램(게임)에서 기다리던 작업을 계속할 이유가 없어졌을 때, 토큰으로 취소하게 됩니다.

(유니테스크 간단 정리는 아래 포스트에 해두었어용)

https://bulletprooves.tistory.com/87

 

[Unity] UniTask

UniTask 와 코루틴을 비교해서 설명하면 이해가 쉽다고 합니다. UniTaskUnity 에서 사용하는 Task 대체 라이브러리라서 UniTask 라고 부릅니다.C# 의 async/await 구문을 좀 더 유니티에 최적화해 사용할 수

bulletprooves.tistory.com

일반적으로 생성 -> 전달 -> 취소 순으로 사용합니다.

먼저 아래와 같이 필드로 둘 수 있습니다.

private CancellationTokenSource _token = null;

 

생성(초기화)는 아래와 같은 단계로 진행하면 됩니다.

void OnEnable()
{
    // 만약 이전 토큰이 있다면 취소 후 해제하는게 좋지요
    _token?.Cancel();
    _token?.Dispose();

    // 새 토큰 발급(생성)
    _token = new CancellationTokenSource();

    // 오브젝트 파괴 시 자동 취소되도록 링크합니다 (선택사항이에용)
    var destroyToken = this.GetCancellationTokenOnDestroy();
    _token = CancellationTokenSource.CreateLinkedTokenSource(_token.Token, destroyToken);

    // 비동기 작업 시작 (안에서 반드시 _token.Token을 전달!)
    RunAsync(_token.Token).Forget();
}

 

await 가 가능한 곳에 넣어 사용할 수 있습니다. (전달)

private async UniTask RunAsync(CancellationToken ct)
{
    try
    {
        // 조건이 만족할 때까지 기다려용 (IsReady 는 부울타입의 변수)
        await UniTask.WaitUntil(() => IsReady, cancellationToken: ct);

        // 일정 시간(1.5초) 기라립니다요
        await UniTask.Delay(TimeSpan.FromSeconds(1.5f), cancellationToken: ct);

        // 리소스 로드(+ 사운드 매니저같은 싱글톤이 있다는 가정 하에 아래처럼 불러오겠습니다용)
        var clip = await SoundManager.Instance.LoadAssetAsync<AudioClip>("path/ang.wav", ct);

        // 메인 스레드에서 업데이트하여 진행하도록 합니다 (필요시 아래처럼 쓰면 돼용)
        await UniTask.SwitchToMainThread(ct);
        if (clip != null)
        	SoundManager.Instance.PlaySound(clip);
    }
    catch (OperationCanceledException)
    {
        // 취소는 정상 흐름이므로 조용히 종료하도록
    }
    catch (Exception ex)
    {
        Debug.LogException(ex); // 진짜 에러만 로깅하도록
    }
}

 

아래 처럼 오브젝트가 그만두게 될 때 취소합니다.

void OnDisable()
{
    _token?.Cancel();   // 진행 중 대기/로드 그냥 바로 중단 때리기
    _token?.Dispose();  // 핸들 해제
    _token = null;
}

 

await

말 그대로 "기다려!" 하는 거 랍니다.

해당 비동기 작업이 끝날 때까지, 메인 스레드(Unity 는 메인스레드)를 막지말고 잠깐 멈추라는 뜻 입니다.

코드의 논리적인 흐름은 동기처럼 읽히게 되지만, 실제 프로그램(게임)의 frame 을 blocking 하지 않습니다.

(잠깐 await 하는 동안, 렌더링이나 입력 처리 등의 다른 일은 계속하게 되는 거죠)

SomeAsync().Forget(); // 기다리지 않습니다, 이 메서드는 그냥 무시~
await SomeAsync(); // 끝날 때까지 논리적으로 대기합니다

 

Static (정적) 유틸리티로 관리하기 (Facade)

아래처럼 싱글톤으로 토큰을 관리할 수도 있겠습니다만,

public static UniTaskManager Instance { get; private set; }

 

아래 처럼 static class 유틸리티 처럼 쓰는 것도 간단하고 괜찮은 방법입니다.

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

public static class UniTaskManager
{
    public static void CancelUniTaskToken(ref CancellationTokenSource cts, GameObject owner, CancellationToken parent = default)
    {
        // #1 이전 토큰 정리 해버리기
        try
        {
            if (cts != null && !cts.IsCancellationRequested)
                cts.Cancel();
        }
        catch (ObjectDisposedException)
        {
        	//이미 해제된 경우에는 무시
        }
        finally
        {
            cts?.Dispose();
            cts = null;
        }

        // #2 owner 가 없으면 새로 만들 필요 없음
        if (owner == null)
            return;

        // #3 owner 파괴 시 취소되는 토큰
        var destroyToken = owner.GetCancellationTokenOnDestroy();
        
        // #4 링크 토큰 소스 생성 (owner 파괴 부모 토큰)
        if (parent.CanBeCanceled)
        {
            cts = CancellationTokenSource.CreateLinkedTokenSource(destroyToken, parent);
        }
        else
        {
            cts = CancellationTokenSource.CreateLinkedTokenSource(destroyToken);
        }
    }
}

 

위처럼 구현해놨을 경우 아래처럼 ref 로 돌려주는 메서드 (helper) 로 사용할 수 있습니다. (Facade 퍼사드)

private CancellationTokenSource _token;

void OnEnable()
{
    UniTaskManager.CancelUniTaskToken(ref _token, this.gameObject);
    RunAsync(_token.Token).Forget();
}

void OnDisable()
{
    UniTaskManager.CancelUniTaskToken(ref _token, this.gameObject);
    // 이전 것 취소(Dispose), 새로 만들 필요 없으면 null 로 두게 됩니다
    _token = null;
}

 

기존의  cts 가 살아있다면, Cancel -> Dispose -> null 순으로 정리되겠습니다.

owner.GetCancellationTokenOnDestroy() 를 링크하면 오브젝트가 파괴될 때 자동으로 취소되게 됩니다.

그러나 상위 흐름에 토큰이 있다먄, 위처럼 parent 로 추가 링크를 둬 글로벌적인 취소도 함께 반영되도록 합니다.

 

why?

왜 이렇게 까지 해두게 되는지는 아래와 같은 이유 때문입니다.

1. 이미 사라진 UI를 대상으로 콜백이 돌아오면 널참조 (NRE) 에러 위험 예방

2. 더 이상 필요 없는 대기나 로딩을 즉시 끊어 리소스 아끼기

3. 화면이 바뀌었는데도 이전 작업이 끝나길 기다리게 됐을 때, 유저기 느낄 게임이 끊긴 현상(?)을 예방

위 같은 이유 때문에 task 관리를 잘 할 수 있도록 UniTask 를 사용하게 됩니다.

정리

마지막으로 요약하면 아래와 같이 체크리스트를 만들 수 있겠습니다.

- 생성 및 초기화 : 이전의 것을 Cancel / Dispose 후, 새롭게 CancellationTokenSource()

- 전달 : 모든 await 가능한 호출에 cancellationToken: _token.Token 하여 전달

- 정리 : OnDisable, OnDestroy 등 에서 Cancel / Dispose / null

( - 예외 처리 : OperationCanceledException 은 정상 취소 -> 조용히 반환)

( - 하위 함수 : 토큰을 “계속 전달”하는 시그니처 유지)

 


 

실컷 놀다가 정리하고 나니 새벽 2시네요~ @.@

그럼 이만 좋은 저녁 되시길 바랍니당 (っ ̯ -。)

'- Unity > let us all UNITE !' 카테고리의 다른 글

[Unity] AsyncOperationHandle  (0) 2025.12.02
[Unity] LoadAssetAsync Release InstantiateAsync ReleaseInstance  (0) 2025.12.02
[Unity, C#] Singleton 싱글톤  (0) 2025.09.07
[Unity] UniTask  (3) 2025.07.10
[Unity] UniTask  (0) 2025.07.03