Task vs UniTask
유니티에서 Task와 UniTask는 비동기 처리를 위한 두 가지 주요 도구입니다.
공통점
1.
비동기 프로그래밍 지원 : async와 await 구문을 사용합니다. 이를 통해 개발자는 비동기 로직을 동기식 코드처럼 간결하고 이해하기 쉽게 작성할 수 있습니다.
차이점
1.
성능 및 최적화
•
Task : 일반적인 .NET 어플리케이션에서의 비동기 작업에 적합하지만, 유니티에서 사용할 경우 추가적인 오버헤드가 발생할 수 있습니다.
•
UniTask : 유니티의 메인 스레드와 렌더링 루프에 최적화되어 있어, 유니티에서 더 효율적입니다. 메모리 할당과 가비지 컬렉션(GC) 부담이 적습니다.
◦
UniTask는 힙 할당을 줄이고 스택에 할당하며, 여러 최적화 방법을 통해 유니티에서 보다 효율적으로 작동합니다. 이에 대한 자세한 내용은 아래에서 설명하겠습니다.
2.
적용 사례
•
Task : I/O 작업, 네트워킹, 데이터베이스 쿼리와 같은 서버 및 일반 애플리케이션 개발에서 주로 사용됩니다.
◦
유니티에서의 로직을 처리하는 것이 아닌 외부에서 처리할 경우, 즉 UniTask를 사용할 수 없을 경우 Task를 사용합니다.
위 내용은 제 개인적인 의견입니다. 그러나 UniTask를 사용하면, 유니티 내부에서 프로그래머들의 코딩 규칙을 제대로 정립하기 위해, Task 대신 UniTask를 사용하는 것이 좋다고 생각합니다.
•
UniTask : 유니티의 애니메이션, 씬 로딩, 코루틴 대체 등 유니티 내 작업에 적합합니다.
3.
라이브러리 및 의존성
•
Task: 별도의 라이브러리 설치 없이 .NET 표준으로 제공됩니다.
•
UniTask: UniTask 라이브러리를 추가로 설치해야 사용할 수 있습니다.
UniTask가 성능 상으로 더 뛰어난 이유
GC 부담이 적다
1.
메모리 할당 최소화 : UniTask는 내부적으로 메모리 할당을 최소화하기 위해 설계되었습니다. Task와 비교하여, UniTask는 실행 중에 발생하는 힙 할당을 줄이고, 필요한 경우 스택 할당을 사용하여 가비지 컬렉션의 압력을 감소시킵니다.
a.
힙 할당을 줄이면 GC의 작동이 줄어드는 이유는 아래에 C#의 GC 동작 방식을 간략하게 설명하였습니다.
2.
값 타입(Value Type) 사용 : UniTask는 값 타입을 사용하여 구현됩니다. 값 타입은 힙이 아닌 스택에 할당되므로 가비지 컬렉터의 작업량을 줄일 수 있습니다. 반면, Task는 참조 타입이며, 객체가 힙에 할당되어 가비지 컬렉터에 더 많은 부담을 주게 됩니다.
3.
풀링 메커니즘 : UniTask는 내부적으로 풀링 메커니즘을 사용하여 객체를 재사용합니다. 이는 새로운 객체를 생성하는 대신 이미 생성된 객체를 재사용함으로써 메모리 할당과 가비지 컬렉션 부담을 줄이는 데 도움이 됩니다.
a.
UniTask 풀링 예제 코드
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskExample : MonoBehaviour
{
async UniTaskVoid SimpleAsyncOperation()
{
// 비동기 작업 수행
await UniTask.Delay(1000); // 1초 지연 -> 이 부분을 내부적으로 풀링해서
Debug.Log("Async Operation Completed");
}
}
C#
복사
b.
Task 풀링되지 않는 예제 코드
using System.Threading.Tasks;
using UnityEngine;
public class TaskExample : MonoBehaviour
{
async Task SimpleAsyncOperation()
{
// 비동기 작업 수행
await Task.Delay(1000); // 1초 지연 -> 이 부분은 풀링되지 않음. GC발생
Debug.Log("Async Operation Completed");
}
}
C#
복사
4.
유니티 특화 최적화 : UniTask는 유니티의 메인 스레드와 게임 루프에 특화되어 있어, 유니티의 실행 환경에 맞는 메모리 관리와 성능 최적화가 가능합니다. 이를 통해 유니티의 특정한 가비지 컬렉션 요구사항에 맞추어 성능을 개선할 수 있습니다.
게임 루프와의 통합
1.
프레임 업데이트와의 동기화 : 유니티 게임 루프는 Update(), FixedUpdate(), LateUpdate() 등의 메서드를 통해 각 프레임에서 실행됩니다. UniTask는 이러한 프레임 업데이트 메커니즘과 동기화되어, 비동기 작업이 게임 루프에 영향을 미치지 않도록 관리합니다. 예를 들어, UniTask는 프레임의 시작이나 종료 시점에 비동기 작업을 처리할 수 있습니다.
a.
프레임 레이트 유지 : UniTask가 프레임의 시작이나 종료 시점에 비동기 작업을 처리함으로써, 게임의 주요 렌더링 작업과 충돌하지 않게 됩니다. 이는 게임의 부드러운 실행을 보장하는 데 도움이 됩니다.
b.
메인 스레드의 부하 최소화 : 유니티의 메인 스레드는 렌더링, 물리 계산, 사용자 입력 처리 등 많은 중요한 작업을 처리합니다. UniTask는 프레임의 시작과 끝에서 비동기작업을 수행함으로써, 메인 스레드에서 발생하는 부하를 균등하게 분산시킵니다.
i.
a,b 이점이 적용되는 예시 코드
using Cysharp.Threading.Tasks;
using UnityEngine;
public class FrameTaskExample : MonoBehaviour
{
async UniTaskVoid Start()
{
// 프레임의 종료에서 추가 비동기 작업
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
// 이 곳에서 작업을 시작하면, 우의 추가적인 유니티 작업들과 충돌되지 않습니다.
// 종료 시점의 비동기 작업
Debug.Log("Async operation at frame end");
}
}
C#
복사
다양한 옵션
1.
취소 토큰(Cancellation Token) 지원
a.
UniTask는 CancellationToken을 지원하여, 비동기 작업을 유연하게 취소할 수 있는 기능을 제공합니다. 이는 게임 개발 시 특정 상황에서 비동기 작업을 중단해야 할 필요가 있을 때 유용합니다.
b.
예시코드
using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;
public class ResourceLoader : MonoBehaviour
{
private CancellationTokenSource cancellationTokenSource;
void Start()
{
cancellationTokenSource = new CancellationTokenSource();
LoadResourcePeriodically(cancellationTokenSource.Token).Forget();
}
async UniTaskVoid LoadResourcePeriodically(CancellationToken cancellationToken)
{
var uniTaskCompletionSource = new UniTaskCompletionSource();
while (!cancellationToken.IsCancellationRequested)
{
// 리소스 로드 로직 (예시)
Debug.Log("Loading resource...");
// 여기에 리소스 로드 로직을 추가
try
{
// 일정 시간 대기 (예: 5초)
await UniTask.Delay(5000, cancellationToken: cancellationToken);
// 취소 되었다면, 아래 로직은 실행되지 않습니다.
// 로드 완료
uniTaskCompletionSource.TrySetResult();
// 다음 로드를 위해 UniTaskCompletionSource 재설정
uniTaskCompletionSource = new UniTaskCompletionSource();
}
catch (OperationCanceledException)
{
Debug.Log("Resource loading cancelled");
break; // while 루프를 빠져나감
}
}
Debug.Log("Resource loading cancelled");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// 취소 토큰을 사용하여 비동기 작업을 취소
cancellationTokenSource.Cancel();
}
}
}
C#
복사
2.
다양한 기다림 옵션
a.
UniTask는 유니티 환경에 특화된 다양한 기다림(Wait) 옵션을 제공합니다
•
UniTask.WaitForSeconds : 실제 시간 기반의 대기를 수행합니다.
•
UniTask.WaitForEndOfFrame : 현재 프레임의 끝까지 대기합니다.
•
UniTask.WaitForFixedUpdate : 다음 FixedUpdate 사이클까지 대기합니다.
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskExample : MonoBehaviour
{
async UniTaskVoid Start()
{
await UniTask.Yield(PlayerLoopTiming.Update);
Debug.Log("After Update");
await UniTask.Yield(PlayerLoopTiming.FixedUpdate);
Debug.Log("After FixedUpdate");
await UniTask.DelayFrame(1);
Debug.Log("One frame later");
}
}
C#
복사