Unity

[Unity][개념] UniTask VS 코루틴

usingsystem 2024. 9. 10. 14:45
728x90

 UniTask는 기존의 코루틴보다 더 나은 비동기 처리를 가능하게 해주는 강력한 라이브러리입니다. 구체적인 장점과 사용 예시를 통해 UniTask의 유용성을 알아보겠습니다.

UniTask를 사용하게 된 이유:

  1. 코루틴의 예외 처리 제한: 코루틴은 try-catch로 예외 처리를 할 수 없어서 안정성이 떨어집니다.
  2. 코루틴의 리턴 타입 제한: 코루틴은 리턴 타입이 없어 실행 결과를 리턴하려면 콜백을 사용해야 합니다. 이로 인해 복잡한 콜백 지옥이 발생할 수 있습니다.
  3. Task의 성능 문제: C#의 async Task는 힙 할당으로 인해 가비지 생성이 많아 잦은 호출이 어렵습니다. 하지만 UniTask는 struct 기반이어서 가비지 생성 문제가 없습니다.
  4. 선형적인 코드 흐름: UniTask를 사용하면 콜백 없이 선형적으로 코드가 작성되어 코드가 훨씬 직관적이고 관리하기 쉬워집니다.
  5. 유니티 에디터 툴에서의 활용: EditorCoroutines와 같이 유니티 에디터 스크립트 작성 시에도 UniTask를 사용하면 코드 관리가 용이합니다.

코루틴과 UniTask 비교 예시

아래 예시에서는 유저의 레벨을 가져오는 비동기 작업을 코루틴과 UniTask로 구현한 예시를 비교합니다.

코루틴 예시:

코루틴을 사용하여 유저 레벨을 가져오는 코드입니다. 콜백을 사용하고 있어 가독성이 떨어지고, 예외 처리가 불가능합니 다.

IEnumerator GetUserLevel(string id, System.Action<int> callback)
{
    // 예외 처리 불가
    var req = UnityWebRequest.Get($"https://.../api/{id}");
    yield return req.SendWebRequest();
    
    if (req.result != UnityWebRequest.Result.Success)
    {
        Debug.LogError(req.error);
        callback(-1);
    }
    else
    {
        callback(int.Parse(req.downloadHandler.text));
    }
}

void tast()
{
    StartCoroutine(GetUserLevel("id", id =>
    {
        Debug.Log($"User Level: {id}");
    }));
}

 

이 예제는 웹 서버에서 유저의 레벨을 가져오는 코드입니다. 코루틴을 사용하면 콜백을 사용해야 하며, 예외 처리가 불가능한 단점이 있습니다.

UniTask 예시

UniTask를 사용하여 동일한 작업을 수행하는 코드입니다. try-catch로 예외 처리를 할 수 있으며, 코드 흐름이 선형적이고 직관적입니다.

async UniTask<int> GetUserLevelAsync(string userName)
{
    try
    {
        var req = UnityWebRequest.Get($"https://.../user/{userName}");
        await req.SendWebRequest();

        if (req.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError(req.error);
            return -1;
        }

        return int.Parse(req.downloadHandler.text);
    }
    catch (Exception e)
    {
        Debug.LogError($"Exception: {e.Message}");
        return -1;
    }
}

async void Logic()
{
    try
    {
        int level = await GetUserLevelAsync("user1");
        Debug.Log($"User Level: {level}");
    }
    catch (Exception e)
    {
        Debug.LogError($"Unhandled Exception: {e.Message}");
    }
}

UniTask를 사용하면 코드는 더 직관적이고 예외 처리가 가능해집니다. 코드의 흐름이 명확해지고 가독성이 좋아집니다.

UniTask의 특징

UniTask는 다음과 같은 특징을 가지고 있습니다:

  • Zero Allocation: UniTask는 struct 기반으로 힙 할당이 없어 가비지 생성을 줄입니다.
  • 유니티의 Async Operations 지원: 어드레서블 같은 비동기 작업을 await으로 기다릴 수 있습니다.
  • 유니티 메인 쓰레드 기반: UniTask는 유니티의 메인 쓰레드에서 작동해 WebGL/WASM에서도 사용할 수 있습니다.
  • Cancellation Token 지원: 작업 취소를 간단하게 관리할 수 있으며, 오브젝트가 삭제될 때 자동으로 작업을 취소할 수 있습니다.
  • 기존 Task와의 호환성: 기존 C# Task, ValueTask와 높은 호환성을 보입니다.

비동기 UI 애니메이션 예시:

UniTask와 DoTween을 사용하여 비동기적으로 UI 애니메이션을 처리하는 예시입니다. await를 사용해 순서대로 실행하며, 필요하지 않은 부분에서는 await를 생략하여 비동기적으로 실행합니다.

async UniTask StartTitleAnimation()
{
    // 초기화면 대기
    await UniTask.Delay(1500);
    
    // 텍스트 상태 초기화
    titleText.rectTransform.anchoredPosition = new Vector2(0, titleText.rectTransform.sizeDelta.y / 2);
    titleText.text = "Loading...";
    titleText.gameObject.SetActive(true);
    
    await titleText.rectTransform.DOAnchorPosY(-330f, 0.5f).SetEase(Ease.OutCirc);
    await UniTask.Delay(700);

    // 텍스트 상태 변경
    titleText.text = "The Black";
    titleText.fontSize = 100;
    titleText.transform.localScale = new Vector3(2, 2, 2);
    
    // 텍스트 커짐->작아짐
    await titleText.transform.DOScale(1, 0.5f).SetEase(Ease.OutBack);

    // pressAnyKey는 대기하지 않음
    pressAnyKeyText.transform.localScale = new Vector3(2, 2, 2);
    pressAnyKeyText.gameObject.SetActive(true);
    pressAnyKeyText.transform.DOScale(1, 0.35f).SetEase(Ease.OutBack);
    
    // 트윈 무한 반복
    await titleText.transform.DOScale(1.1f, 2).SetLoops(-1, LoopType.Yoyo).SetEase(Ease.InOutQuad);
}

void Start()
{
    StartTitleAnimation().Forget();
}

비동기 작업 취소 (Cancellation) 예시:

오브젝트가 삭제되거나 씬이 변경될 때 자동으로 비동기 작업을 중단할 수 있도록 Cancellation Token을 사용하는 방법입니다.

public async UniTaskVoid WhileTest()
{
    var cancellationToken = this.GetCancellationTokenOnDestroy();

    while (true)
    {
        await UniTask.Yield(cancellationToken: cancellationToken);
        Debug.Log(Time.realtimeSinceStartup);
    }
}

public async UniTask Start()
{
    WhileTest();
}

private void OnDestroy()
{
    Debug.Log("오브젝트를 삭제했습니다. 더이상 비동기가 실행되면 안됩니다.");
}

이 예시에서는 UniTask의 CancellationToken을 사용해 오브젝트가 삭제되었을 때 자동으로 비동기 작업을 중단하도록 설정하였습니다. 이를 통해 불필요한 비동기 작업을 방지하고 리소스를 효율적으로 사용할 수 있습니다.

 

UniTask는 기존의 코루틴을 대체하여 비동기 작업을 더욱 효율적으로 관리할 수 있게 해주는 강력한 라이브러리입니다. 특히 복잡한 비동기 로직을 직관적으로 관리할 수 있고, 가비지 생성 문제를 줄여 성능 최적화에 도움을 줍니다. 

 

https://github.com/Cysharp/UniTask

 

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

728x90