본문 바로가기
게임 개발(유니티)/멋쟁이 사자처럼 3기_회고록

Uni Task는 무엇인가?

by goraku97 2025. 1. 5.

유니티 코루틴의 단점

- 서버로부터 받은 값을 IEnumrator Function()에서 리턴할 수 없음

  (특정 값을 return 키워드를 사용해 바로 반환할 수 없다는 의미)

 

- StartCoroutin 을 사용할 때마마 GC할당이 높음.

  특히 yield return new 를 사용할때마다 New 로 인한 할당이 지속적으로 발생

// GC(가비지 컬렉션) : 사용하지 않는 메모리를 정리하는 작업

// GC가 실행되면 프로그램이 잠시 멈추는 일이 발생할 수 있음.

// GC가 자주 동작하면 성능 저하와 프레임 드랍이 발생할 가능성이 높아짐.

, GC할당이 높아지면, 프레임 드랍이 많이 발생하고, 메모리 낭비가 심해짐.

 

UniTask 의 장점

- 서버로부터 받은 값을 Return 할 수 있다.

 

- Struct 기반으로 제작되어 있어, Zero Allocation이 특징

// Zero Allocation : 실행 중에 추가적인 메모리를 할당하지 않는다는 의미

// , 프로그램이 UniTask를 사용할 때 새로운 메모리 객체를 생성하거나

   가비지 컬렉션(GC)을 유발하지 않도록 설계되었다는 것을 뜻

 

- UniTask.Yield, UniTask.Dealy, UniTask.DelayFrame 처럼

   Coroutine과 유사한 기능을 제

 

- 메모리 누수를 방지하기 위한 TaskTracker 창 지원.

// TaskTracke : UniTask로 생성된 비동기 작업들을 추적하고 관리하는 도구

// 비동기 작업 : 시간이 오래 걸리는 작업을 다른 작업과 동시에 실행하도록 처리하는

                         프로그래밍 방식.

  즉, 다른 코드의 실행을 막지 않고 병렬적으로 실행되는 작업

                        

// 메모리 누수 방지디버깅 편의성을 제공하는 역할을 수행

// 메모리 누수: 프로그램이 더 이상 사용하지 않는 메모리를 해제하지 않고 계속 점유하는 상황                   

// 시스템의 사용 가능한 메모리가 줄어들고, 결국 프로그램이나 시스템 성능이 저하되거나 충돌이 발생할 수 있음.

 

1. 기존 코루틴 방식과 UniTask 방식으로 비동기 작업(시간 지연) 을 처리할 때

public class TestCode : MonoBehaviour
{
    // 1. 기존 코루틴 방식으로 비동기 작업(시간 지연) 을 처리할때
    private IEnumerator WaitSecond()
    {
        yield return new WaitForSeconds(1f);
        Debug.Log("1초가 지났다");
    }
    
    // 1. UniTask 방식으로 비동기 작업(시간 지연) 을 처리할때
    // 유니티의 코루틴보다 더 성능이 좋고 코드가 간결함
    private async UniTaskVoid WaitSecondAsync()
    {
        // await: 현재 작업을 비동기로 처리하고, 대기한 후에 다음 줄을 실행
        // UniTask.Delay : 주어진 시간 동안 비동기적으로 대기
        await UniTask.Delay(TimeSpan.FromSeconds(1f));
        Debug.Log("1초가 지났다");
    }

    private void Start()
    {
        // 1. 기존 코루틴 방식
        StartCoroutine(WaitSecond());
        // 1. UniTask 방식
        WaitSecondAsync().Forget();
        
    }
}

 

2. 기존 코루틴 방식과 UniTask 방식으로 타임 스케일을 무시하는 작업을 처리할 때

타임 스케일

public class TestCode : MonoBehaviour
{
    // 2. 기존 코루틴 방식으로 타임 스케일을 무시하는 작업을 처리할 때(타임 
    private IEnumerator WaitSecond()
    {
        // 타임 스케일이 0 이되어도 동작하는 코드
        yield return new WaitForSecondsRealtime(1f);
        Debug.Log("1초가 지났다");
    }
    
    // 2. UniTask 방식으로 타임 스케일을 무시하는 작업을 처리할 때
    // 유니티의 코루틴보다 더 성능이 좋고 코드가 간결함
    private async UniTaskVoid WaitSecondAsync()
    {
        // await: 현재 작업을 비동기로 처리하고, 대기한 후에 다음 줄을 실행
        // UniTask.Delay : 주어진 시간 동안 비동기적으로 대기
        // 타임 스케일이 0 이되어도 동작하는 코드
        await UniTask.Delay(TimeSpan.FromSeconds(1f), DelayType.UnscaledDeltaTime);
        Debug.Log("1초가 지났다");
    }

    private void Start()
    {
        // 타임 스케일을 0으로 설정
        Time.timeScale = 0;
        // 2. 기존 코루틴 방식
        StartCoroutine(WaitSecond());
        // 2. UniTask 방식
        WaitSecondAsync().Forget();
        
    }
}

 

3. 기존 코루틴 방식과 UniTask 방식으로 어떤 기능이 특정 조건이 되었을때 실행하도록 하고 싶을 때

public class TestCode : MonoBehaviour
{
    // _count 맴버변수 생성
    public int _count;
    
    // 3. 기존 코루틴 방식으로 어떤 기능이 특정 조건이 되었을때 실행하도록 하는 코드
    private IEnumerator Wait3Count()
    {
        // _count 맴버변수가 3이 되어야 하기의 Debug.Log가 실행됨
        yield return new WaitUntil(() => _count == 3);
        Debug.Log("카운트가 3이 되었다.");
    }
    
    // 3. UniTask 방식으로 어떤 기능이 특정 조건이 되었을때 실행하도록 하는 코드
    // 유니티의 코루틴보다 더 성능이 좋고 코드가 간결함
    private async UniTaskVoid Wait3CountAsync()
    {
        // _count 맴버변수가 3이 되어야 하기의 Debug.Log가 실행됨
        await UniTask.WaitUntil(() => _count == 3);
        Debug.Log("카운트가 3이 되었다.");
    }

    private void Start()
    {
       
        // 3. 기존 코루틴 방식
        StartCoroutine(Wait3Count());
        // 3. UniTask 방식
        Wait3CountAsync().Forget();
        
    }
}

 

4. 기존 코루틴 방식과 UniTask 방식으로 웹에 있는 이미지URL주소를 가져와서

    텍스쳐 파일 또는 스프라이트로 바꾸는 작업을 하고 싶을 때

 

- 코루틴 방식

public class TestCode : MonoBehaviour
{
    // 웹에서 가져온 사과 이미지 URL주소를 맴버변수로 설정
    private const string AppleImage = "https://cdn.mkhealth.co.kr/news/photo/202010/50970_51164_4758.jpg";
    // UI 요소중에 RawImage 를 통해서, 불러온 텍스쳐를 UI단에다 올림
    public RawImage appleimage1;
    
    
    // 4. 기존 코루틴 방식으로 웹에 있는 이미지URL주소를 가져와서 텍스쳐 파일 또는 스프라이트로 바꾸는 작업을 하고 싶을 때
    private IEnumerator WaitGetWebTexture(UnityAction<Texture2D> action)
    {
        // 웹에서 텍스쳐를 받아오기
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(AppleImage);
        yield return request.SendWebRequest();
        // 이미지를 가져오는데  실패하는 코드를 작성
        // or는 Result 타입에 사용되는 메서드로, 하나의 값이 None 또는 Err일 경우 다른 값을 반환하는 방식으로 동작
        if (request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError)
        {
            // 에러의 처리
            Debug.LogError(request.error);
        }
        // 이미지를 가져오는데 성공 코드를 작성
        else
        {
            // 성공 처리
            Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
            // 받아언 텍스쳐를 리턴처리
            action.Invoke(texture);
        }
        
    }
    
    private void Start()
    {
       
        // 4. 기존 코루틴 방식
        StartCoroutine(WaitGetWebTexture(texture =>
        {
            // 매개변수인 appleimage1 를 사용함
            appleimage1.texture = texture; 
        }));
        
    }
}

 

- UniTask 방식

public class TestCode : MonoBehaviour
{
    // 웹에서 가져온 사과 이미지 URL주소를 맴버변수로 설정
    private const string AppleImage = "https://cdn.mkhealth.co.kr/news/photo/202010/50970_51164_4758.jpg";
    // UI 요소중에 RawImage 를 통해서, 불러온 텍스쳐를 UI단에다 올림
    public RawImage appleimage1;
    
    
    // 4. UniTask 방식으로 웹에 있는 이미지URL주소를 가져와서 텍스쳐 파일 또는 스프라이트로 바꾸는 작업을 하고 싶을 때
    private async UniTask<Texture2D> WaitGetWebTextureAsync()
    {
        // 웹에서 텍스쳐를 받아오기
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(AppleImage);
        await request.SendWebRequest();
        
        // or 는 Result 타입에 사용되는 메서드로, 하나의 값이 None 또는 Err 일 경우 다른 값을 반환하는 방식으로 동작
        if (request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError)
        {
            // 실패 처리
            Debug.LogError(request.error);
        }

        // 이미지를 가져오는데 성공 코드를 작성
        else
        {
            // 성공처리
            Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
            // 텍스쳐를 리턴처리
            return texture;
        }
        
        return null;
    }

    private async UniTaskVoid GetImageAsync()
    {
        // 4. UniTask 방식
        Texture2D texture = await WaitGetWebTextureAsync();
        // 매개변수인 appleimage1.을 사용함
        appleimage1.texture = texture;
    }

    private void Start()
    {
        // 4. UniTask 방식으로 실행
        GetImageAsync().Forget();
    }
   
}

 

5. 기존 코루틴 방식과 UniTask 방식으로 중간에 종료, 또는 일시정지 하는 기능을 구현하고 싶을 때

 

- 코루틴 방식

public class TestCode : MonoBehaviour
{
  // 5. 코루틴 자료형 생성
  private Coroutine _coroutine;
  
  
  // 5. 기존 코루틴 방식으로 중간에 종료, 또는 일시정지 하는 기능을 구현하고 싶을 때
  private IEnumerator Wait3Seconds()
  {
    yield return new WaitForSeconds(3f);
    Debug.Log("3초가 지났습니다.");
  }

  private void Start()
  {
    // 5. 기존 코루틴 방식
    _coroutine = StartCoroutine(Wait3Seconds());
  }

  private void Update()
  {
    // 5. 스페이스바를 누르면 StopCoroutine(); 실행
    if (Input.GetKeyDown(KeyCode.Space))
    {
      StopCoroutine(_coroutine);
    }
  }
}

 

- UniTask 방식

// CancellationTokenSource : Cancel 타이밍을 원할 때 처리 가능(직접적인 Dispose호출 필요)

// this.GetCancellationTokenOnDestroy : Destroy될때 Cancel 및 Dispose 를 수행함

public class TestCode : MonoBehaviour
{
  // CancellationTokenSource 맴버변수를 생성
  private CancellationTokenSource _source = new();

  // 5. UniTask 방식으로 중간에 종료, 또는 일시정지 하는 기능을 구현하고 싶을 때
  private async UniTaskVoid Wait3Second()
  {
    await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: _source.Token);
    Debug.Log("3초가 지났다.");
    
  }
  
  // Wait3Second() 함수를 실행
  private void Start()
  {
    Wait3Second().Forget();
  }

  // 스페이스바를 눌렀을때 중지
  private void Update()
  {
    if (Input.GetKeyDown(KeyCode.Space))
    {
      _source.Cancel();
    }
  }
}

 

++ 추가적으로 오브젝트가 삭제되거나 비활성일때 취소시키고 싶으면 ++

public class TestCode : MonoBehaviour
{
  // CancellationTokenSource 맴버변수를 생성
  private CancellationTokenSource _source = new();

  // 5. UniTask 방식으로 중간에 종료, 또는 일시정지 하는 기능을 구현하고 싶을 때
  private async UniTaskVoid Wait3Second()
  {
    await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: _source.Token);
    Debug.Log("3초가 지났다.");
    
  }
  
  // Wait3Second() 함수를 실행
  private void Start()
  {
    Wait3Second().Forget();
  }

  // 스페이스바를 눌렀을때 중지
  private void Update()
  {
    if (Input.GetKeyDown(KeyCode.Space))
    {
      _source.Cancel();
    }
  }
  
  // 오브젝트가 삭제되면 진행중인 Task를 취소하고, Dispose(리소스나 객체를 정리 및 헤제) 하도록 처리
  private void OnDestroy()
  {
    _source.Cancel();
    _source.Dispose();
  }
  
  // _source 가 null 이 아니면, Dispose(리소스나 객체를 정리 및 헤제) 하도록 처리
  private void OnEnable()
  {
    if(_source != null)
      _source.Dispose();
    
    // 그 다음에 새로운 new 를 처리해서 CancellationTokenSource 를 대입하게 만듬
    _source = new CancellationTokenSource();
  }
  
  // 오브젝트가 비활성화 되면  Task를 취소하도록 처리
  private void OnDisable()
  {
    _source.Cancel();
  }
}

 

++ 게임 오브젝트가 씬에서 제거될때 자동으로 task를 중단만(비활성화는 포함 x)시키려면 ++

public class TestCode : MonoBehaviour
{
  // 5. UniTask 방식으로 중간에 종료, 또는 일시정지 하는 기능을 구현하고 싶을 때
  private async UniTaskVoid Wait3Second()
  {
    // this.GetCancellationTokenOnDestroy() : 게임 오브젝트가 씬에서 제거되면 자동으로 task를 중단
    await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: this.GetCancellationTokenOnDestroy());
    Debug.Log("3초가 지났다.");
    
  }
  
  // Wait3Second() 함수를 실행
  private void Start()
  {
    Wait3Second().Forget();
  }
}

 

** 6.  UniTask 방식으로 실행중인 Task 추적하기 (기존 코루틴 방식에서는 존재 X)

// ** 6. UniTask 방식으로 실행중인 Task 추적하기 (기존 코루틴 방식에서는 존재 X)
// Window -> UniTask Tracker 를 들어가서 Enable AutoReload, Enable Tracking, Enable StackTrace 들을
// 전부 활성화시키고 게임을 실행. 실행시키면 현재 진행중인 Tracker 를 추적해서, 메모리가 누수가 되고 있는지  아닌지 확인가능.
public class TestCode : MonoBehaviour
{
  // 3초후에 해당 코드를 실행
  private async UniTaskVoid Wait3Seconds()
  {
    await UniTask.Delay(TimeSpan.FromSeconds(3));
    Debug.Log("3초가 지났다.");
  }

  private void Start()
  {
    Wait3Seconds().Forget();
  }
}