Search

Task vs UniTask

Class
기타 개발
Type
기타 개발
Created
2024/01/09 01:18
updated
2024/01/29 00:22
날짜

Task vs UniTask

유니티에서 TaskUniTask비동기 처리를 위한 두 가지 주요 도구입니다.
공통점
1.
비동기 프로그래밍 지원 : asyncawait 구문을 사용합니다. 이를 통해 개발자는 비동기 로직을 동기식 코드처럼 간결하고 이해하기 쉽게 작성할 수 있습니다.
차이점
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.
UniTaskCancellationToken을 지원하여, 비동기 작업을 유연하게 취소할 수 있는 기능을 제공합니다. 이는 게임 개발 시 특정 상황에서 비동기 작업을 중단해야 할 필요가 있을 때 유용합니다.
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#
복사