[Unity] UniTask 사용해보기

이준영·2026년 1월 16일

오늘은 안드로이드에서 Stages.Json 파일을 찾지 못해서 비동기 작업으로 코드를 변경하다가 UniTask를 사용해 보게 되어서 UniTask에 대해서 정리하려고 한다.

UniTask

  • 표준 C#의 Task(System.Threading.Tasks.Task)를 유니티 환경에 최적화하여 재작성한 라이브러리.

  • 유니티는 단일 스레드 기반의 프레임 루프를 사용하기 때문에, 범용적인 .NET Task를 그대로 쓰면 성능 저하나 관리의 어려움이 발생할 수 있음.

성능 및 메모리 최적화 (Zero Allocation)

가장 큰 차이점은 메모리 할당(Allocation) 방식.

  • Task (Reference Type):
    Task는 클래스(Class)이므로 생성될 때마다 힙(Heap) 메모리에 할당.
    비동기 작업을 많이 사용하는 게임의 경우, 빈번한 GC(Garbage Collection)를 유발하여 프레임 드랍(렉)의 원인.

  • UniTask (Value Type):
    UniTask는 구조체(Struct) 기반.
    비동기 메서드가 완료될 때까지 상태를 보관하는 데 필요한 메모리를 최대한 줄이고, 내부적으로 객체 풀링(Object Pooling)을 사용하여 힙 할당을 거의 0(Zero Allocation)으로 유지.

유니티 엔진과의 완벽한 통합

표준 Task는 유니티의 생명주기(Update, FixedUpdate 등)를 알지 못하지만, UniTask는 유니티 엔진의 핵심 루프 위에서 동작하도록 설계됨.

  • 다양한 유니티 타입 대기(await): AsyncOperation (에셋 로딩), ResourceRequest, UnityWebRequest, 심지어는 기존의 Coroutine까지도 직접 await 가능.

  • 프레임 제어: UniTask.Yield(), UniTask.NextFrame(), UniTask.WaitForFixedUpdate() 등을 통해 특정 프레임 타이밍까지 비동기적으로 대기하는 것이 매우 쉽움.

  • 타임스케일 영향 조절: Delay를 사용할 때 ignoreTimeScale 옵션을 통해 게임의 정지 상태 유무와 상관없이 대기 시간을 조절 가능.

강력한 취소(Cancellation) 관리

비동기 작업 중 해당 오브젝트가 파괴(Destroy)되면 작업이 중단되어야 함. Task는 이를 수동으로 처리하기 까다롭지만, UniTask는 유니티 전용 기능을 제공.

  • GetCancellationTokenOnDestroy: MonoBehaviour가 파괴될 때 비동기 작업을 자동으로 취소할 수 있는 토큰을 쉽게 얻을 수 있어 메모리 누수를 방지.

  • TaskTracker: 전용 윈도우(Window -> UniTask Tracker)를 통해 현재 실행 중인 모든 UniTask 목록을 확인하고, 어떤 작업에서 누수가 발생하는지 실시간으로 디버깅 가능.

Task vs UniTask 비교 요약

항목System.Threading.Tasks.TaskUniTask (Cysharp)
타입클래스 (Reference Type)구조체 (Value Type)
GC 할당높음 (매 작업마다 할당)매우 낮음 (Zero-Allocation 지향)
유니티 통합낮음 (별도 래퍼 필요)높음 (AsyncOperation 등 직접 지원)
실행 엔진.NET ThreadPool / SynchronizationContextUnity PlayerLoop
특수 기능표준 비동기 기능프레임 단위 제어, Tracker 지원
WebGL 지원제한적임 (멀티스레드 이슈)완벽 지원 (단일 스레드 기반)

머 위에는 대충 Gemini형이 정리해 준 내용인데, 읽어보니 흥미로운 내용이 좀 있었다.

  1. Cancellation
  2. TaskTracker

TaskTracker 테스트

셋팅

오브젝트 생성
  • 코드 작성
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;        
using System;

public class MagicCaster : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            SafeMagicAsync(this.GetCancellationTokenOnDestroy()).Forget();
        }
        
        if (Input.GetKeyDown(KeyCode.W))
        {
            LeakyMagicAsync().Forget();
        }
        
        if (Input.GetKeyDown(KeyCode.E))
        {
            Destroy(this.gameObject);
        }
    }
    
    private async UniTaskVoid SafeMagicAsync(CancellationToken token)
    {
        try
        {
            await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: token);
        }
        catch (OperationCanceledException) { }
    }
    
    private async UniTaskVoid LeakyMagicAsync()
    {
        await UniTask.Delay(TimeSpan.FromSeconds(3));
    }
}
  • Q, W, E로 제어하는 오브젝트를 하나 생성했다.
  • Q : cancellationToken이 있는 UniTask
  • W : cancellationToken이 없는 UniTask
  • E : 오브젝트 파괴

그리고 Unity Editor에서 Window -> UniTask Tracker로 Tracker창을 켰다.

UniTask Tracker

Test

Q -> E
  • cancellationToken을 사용한 경우이다.
  • MagicCaster가 파괴됨과 동시에 UniTask Tracker에서 실행되고 있던 비동기 코드가 사라지는 것을 볼 수 있다.
W -> E
  • cancellationToken을 사용하지 않은 경우이다.
  • MagicCaster가 파괴되었지만 UniTask Tracker에서 실행되고 있던 비동기 코드가 사라지지 않고 남아 있는 것을 볼 수 있다.
profile
게임 개발자가 되기 위해서 공부하는 중입니다.

0개의 댓글