오늘은 안드로이드에서 Stages.Json 파일을 찾지 못해서 비동기 작업으로 코드를 변경하다가 UniTask를 사용해 보게 되어서 UniTask에 대해서 정리하려고 한다.
표준 C#의 Task(System.Threading.Tasks.Task)를 유니티 환경에 최적화하여 재작성한 라이브러리.
유니티는 단일 스레드 기반의 프레임 루프를 사용하기 때문에, 범용적인 .NET Task를 그대로 쓰면 성능 저하나 관리의 어려움이 발생할 수 있음.
가장 큰 차이점은 메모리 할당(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 옵션을 통해 게임의 정지 상태 유무와 상관없이 대기 시간을 조절 가능.
비동기 작업 중 해당 오브젝트가 파괴(Destroy)되면 작업이 중단되어야 함. Task는 이를 수동으로 처리하기 까다롭지만, UniTask는 유니티 전용 기능을 제공.
GetCancellationTokenOnDestroy: MonoBehaviour가 파괴될 때 비동기 작업을 자동으로 취소할 수 있는 토큰을 쉽게 얻을 수 있어 메모리 누수를 방지.
TaskTracker: 전용 윈도우(Window -> UniTask Tracker)를 통해 현재 실행 중인 모든 UniTask 목록을 확인하고, 어떤 작업에서 누수가 발생하는지 실시간으로 디버깅 가능.
| 항목 | System.Threading.Tasks.Task | UniTask (Cysharp) |
|---|---|---|
| 타입 | 클래스 (Reference Type) | 구조체 (Value Type) |
| GC 할당 | 높음 (매 작업마다 할당) | 매우 낮음 (Zero-Allocation 지향) |
| 유니티 통합 | 낮음 (별도 래퍼 필요) | 높음 (AsyncOperation 등 직접 지원) |
| 실행 엔진 | .NET ThreadPool / SynchronizationContext | Unity PlayerLoop |
| 특수 기능 | 표준 비동기 기능 | 프레임 단위 제어, Tracker 지원 |
| WebGL 지원 | 제한적임 (멀티스레드 이슈) | 완벽 지원 (단일 스레드 기반) |
머 위에는 대충 Gemini형이 정리해 준 내용인데, 읽어보니 흥미로운 내용이 좀 있었다.
| 오브젝트 생성 |
|---|
![]() |
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));
}
}
그리고 Unity Editor에서 Window -> UniTask Tracker로 Tracker창을 켰다.
| UniTask Tracker |
|---|
![]() |
| Q -> E |
|---|
![]() |
| W -> E |
|---|
![]() |