UniTask : GitLink
GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.
Provides an efficient allocation free async/await integration for Unity. - Cysharp/UniTask
github.com
UniTask란 무엇인가?
- UniTask는 Unity 환경에 특화된 고성능 async/await 라이브러리입니다. Unity의 기본 코루틴(Coroutine)과 C# 표준 Task의 단점을 보완하고 대체하기 위해 만들어졌습니다.
주요 특징
- Unity의 비동기 작업(AsyncOperation 등)과 생명주기 이벤트(Update, LateUpdate 등)를 await키워드로 쉽게 처리할 수 있습니다.
- 비동기 LINQ(UniTaskAsyncEnumerable)를 지원하여 데이터를 비동기 스트림으로 다룰 수 있습니다.
- 가장 큰 장점은 제로 할당(Zero Allocation)에 가깝게 동작하여 GC(Garbage Collection) 부담을 크게 줄여준다는 것입니다.
UniTask가 기존 async/await (Task)보다 성능이 뛰어난 이유와 제로 할당의 원리
- UniTask의 핵심적인 성능 우위는 제로 할당 원리에서 나옵니다.
주의사항:
- 디버그 빌드에서는 실제로 할당이 발생할 수 있습니다. 이는 정상적인 현상이며, 릴리즈 빌드에서는 제로 할당이 달성됩니다.
- 제로 할당은 즉시 완료되는 작업에서 완전히 달성되며, 실제 비동기 흐름에서는 객체 풀링을 통해 할당을 최소화합니다.
1. 기존 Task의 할당 문제
- C#의 표준 async Task를 사용하면, 비동기 메서드가 호출될 때마다 보이지 않는 곳에서 여러 메모리 할당이 발생합니다.
- Task 객체 할당: async 메서드는 항상 Task객체를 힙(Heap) 메모리에 생성합니다.
- 상태 머신(State Machine)의 박싱(Boxing): 컴파일러가 생성하는 상태 머신(struct)이 힙에 할당되는 박싱 과정이 발생할 수 있습니다.
- 콜백 델리게이트(Delegate) 할당: 비동기 작업이 완료되었을 때 다음 코드를 실행하기 위한 델리게이트가 할당됩니다.
이러한 작은 할당들이 반복적으로 쌓이면 GC의 부담을 높여 특히 성능에 민감한 게임에서 프레임 드랍(끊김 현상)의 원인이 될 수 있습니다.
2. UniTask의 제로 할당 해결 원리
UniTask는 이러한 할당 문제를 다음과 같은 방법으로 해결합니다.
- UniTask는 struct(구조체): Task가 class(클래스)인 것과 달리 UniTask는 struct(구조체)입니다. 따라서 즉시 완료되는 동기적인 코드 흐름에서는 힙 할당 없이 스택에서 처리되어 할당이 발생하지 않습니다.
- 객체 풀링 (Object Pooling) 활용: 이것이 가장 핵심적인 원리입니다.
* 비동기 흐름을 처리하기 위해 필요한 Promise 객체(상태 머신을 실행하는 객체)를 매번 새로 생성(`new`)하지 않습니다.
* 대신 미리 만들어 둔 객체 풀(Pool)에서 빌려와 사용하고, 비동기 작업이 완료되면 다시 풀에 반납합니다.
* 이러한 재사용 메커니즘 덕분에 async/await 체인을 아무리 많이 사용해도 새로운 힙 메모리 할당이 거의 발생하지 않습니다.
- 제약사항을 통한 최적화: 이 공격적인 풀링 방식에는 한 가지 중요한 제약사항이 따릅니다. 바로 하나의 UniTask 객체는 단 한 번만 await 할 수 있다는 것입니다. await가 끝나는 순간 해당 객체는 풀로 반납되어 재사용을 기다리는 상태가 되기 때문입니다.
결론적으로 UniTask는 struct타입과 핵심적인 객체 풀링(Object Pooling)기술을 결합하여 즉시 완료되는 경우에는 메모리 할당을 완전히 제거하고, 비동기 흐름에서는 할당을 최소화함으로써 GC 부담을 줄여 Unity 프로젝트의 성능을 극대화하는 라이브러리입니다.
사용 예제
public async UniTaskVoid LoadManyAsync()
{
// parallel load.
var (a, b, c) = await UniTask.WhenAll(
LoadAsSprite("foo"),
LoadAsSprite("bar"),
LoadAsSprite("baz"));
}
async UniTask<Sprite> LoadAsSprite(string path)
{
var resource = await Resources.LoadAsync<Sprite>(path);
return (resource as Sprite);
}
public UniTask<int> WrapByUniTaskCompletionSource()
{
var utcs = new UniTaskCompletionSource<int>();
// when complete, call utcs.TrySetResult();
// when failed, call utcs.TrySetException();
// when cancel, call utcs.TrySetCanceled();
return utcs.Task; //return UniTask<int>
}
취소 예제
var cts = new CancellationTokenSource();
cancelButton.onClick.AddListener(() =>
{
cts.Cancel();
});
await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token);
await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
// this CancellationToken lifecycle is same as GameObject.
await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
public class MyBehaviour : MonoBehaviour
{
CancellationTokenSource disableCancellation = new CancellationTokenSource();
CancellationTokenSource destroyCancellation = new CancellationTokenSource();
private void OnEnable()
{
if (disableCancellation != null)
{
disableCancellation.Dispose();
}
disableCancellation = new CancellationTokenSource();
}
private void OnDisable()
{
disableCancellation.Cancel();
}
private void OnDestroy()
{
destroyCancellation.Cancel();
destroyCancellation.Dispose();
}
}
타임아웃 처리 예제
TimeoutController timeoutController = new TimeoutController(); // setup to field for reuse.
async UniTask FooAsync()
{
try
{
// you can pass timeoutController.Timeout(TimeSpan) to cancellationToken.
await UnityWebRequest.Get("http://foo").SendWebRequest()
.WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
timeoutController.Reset(); // call Reset(Stop timeout timer and ready for reuse) when succeed.
}
catch (OperationCanceledException ex)
{
if (timeoutController.IsTimeout())
{
UnityEngine.Debug.Log("timeout");
}
}
}
UniTask의 경우 트래커를 제공합니다.

- Window -> UniTask Tracker
- UniTaskTracker는 디버깅용으로만 설계되었습니다. 추적 및 스택 추적 캡처를 활성화하는 것은 유용하지만 성능에 큰 영향을 미칩니다. 작업 누수를 발견하기 위해 추적 및 스택 추적을 모두 활성화하고, 작업이 완료되면 두 가지 모두 비활성화하는 것이 좋습니다.
참고자료 : 바로가기
UniTask v2 — Zero Allocation async/await for Unity, with Asynchronous LINQ
I’ve previously published UniTask, a new async/await library for Unity, now I’ve rewritten all the code and released new one.
neuecc.medium.com
'유니티 > 유니티 이론' 카테고리의 다른 글
[이론] 에셋 번들 (2) | 2025.06.18 |
---|---|
[이론] Addressable (0) | 2025.06.15 |
[이론] async/await (0) | 2025.06.11 |
[이론] 코루틴 (0) | 2025.06.11 |
[이론] Scriptable Object (2) | 2025.06.10 |