Unity Unitask 사용법

2022. 8. 10. 10:56유니티 unity

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. - GitHub - Cysharp/UniTask: Provides an efficient allocation free async/await integration for Unity.

github.com

 

UniTask는 기존 C#Task는 무겁고 Unity(단일 스레드)와 일치하지 않기 때문에 유니티에게 최적화해서 사용할 수 있게 만들었습니다.

기존 코루틴의 단점은  리턴 값이 없어서 따로 콜백 처리를 해줘야 하고 try-catch 예외처리를 못하고

StartCoroutine와 YieldInstruction에서 가비지가 주로 생성되는 단점이 있습니다.

코루틴을 대체할 수 있는 게 바로 UniTask입니다.

 

기본 사용법은

코루틴

void Start()
{
    
    StartCoroutine(HelloCoroutine());
}
IEnumerator HelloCoroutine()
{
    Debug.Log("안");
    yield return new WaitForSeconds(1);
    Debug.Log("녕");
   
}

/////

UniTask

void Start()
{
    HelloTask();
}

async UniTask HelloTask()
{
    Debug.Log("안");
    //await UniTask.Delay(1000); //1초
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log("녕");
}

////
이렇게도 사용가능

async UniTaskVoid Start()
{
    Debug.Log("안");
    //await UniTask.Delay(1000); //1초
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log("녕");
}

주의할점은

코루틴은 오브젝트가 삭제되거나 비활성화가 되면 자동으로 중지가 되는데

UniTask는 따로 취소처리를 해줘야 합니다.

UniTask Tracker를 보시면 확인이 가능합니다

 

비활성화했는데도 비동기 함수가 안 끝남

 

 

 

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();
}

async UniTaskVoid Start()
{

    Debug.Log("안");
    //await UniTask.Delay(1000,false,PlayerLoopTiming.Update,disableCancellation.Token);
    await UniTask.Delay(TimeSpan.FromSeconds(1),false,PlayerLoopTiming.Update,disableCancellation.Token);
    Debug.Log("녕");
}

이렇게 처리하면

비활성화 시 비동기 함수가 끝남

 

 

 

또 다른 예시 버튼 클릭 시  취소함

 

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);

 

코루틴에서 사용되는 것들은 UniTask로 전부 대체 가능합니다.

//yield return null
await UniTask.Yield();
await UniTask.NextFrame();


///////

//yield return WaitUntil
await UniTask.WaitUntil(() => isActive == false);

// 또다른 사용방법
await UniTask.WaitUntilValueChanged(this, x => x.isActive);

////

// yield return new WaitForSeconds/WaitForSecondsRealtime
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);

 

또 시간 초과처리도 가능합니다.

 

var cts = new CancellationTokenSource();
cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5초 시간초과

try
{
    await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(cts.Token);
}
catch (OperationCanceledException ex)
{
    if (ex.CancellationToken == cts.Token)
    {
       Debug.Log("Timeout");
    }
}

 

다른 취소처리 와 함께 사용을 하려면

var cancelToken = new CancellationTokenSource();
cancelButton.onClick.AddListener(()=>
{
    cancelToken.Cancel(); // 버튼 클릭시 취소
});

var timeoutToken = new CancellationTokenSource();
timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5초경과시 취소

try
{
    // combine token
    var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token);

    await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token);
}
catch (OperationCanceledException ex)
{
    if (timeoutToken.IsCancellationRequested)
    {
        Debug.Log("Timeout.");
    }
    else if (cancelToken.IsCancellationRequested)
    {
        Debug.Log("Cancel clicked.");
    }
}

 

 

UniTask로 3번 클릭 구현

 

async UniTask TripleClick()
{
    // 기본적으로 button.GetCancellationTokenOnDestroy를 사용하여 비동기의 수명을 관리합니다.
    await button.OnClickAsync();
    await button.OnClickAsync();
    await button.OnClickAsync();
    Debug.Log("3번 클릭");
}

 

Unitask로 리턴 값을 받고 싶으면

 

async UniTask Go()
{
    string s = await GetTextAsync(변수넣기);
    Debug.Log(s);
}


async UniTask<string> GetTextAsync(UnityWebRequest req)
{
	try
    { 
        var op = await req.SendWebRequest();
        return op.downloadHandler.text;
    }
    catch(Exception e)
    {
        Debug.LogError(e);
        return "";
    }  
}

 

async UniTask Go()
{
    int i = await GetIntAsync();
    Debug.Log(i); // 1초뒤에 1이 나옴
}
async UniTask<int> GetIntAsync()
{
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    return 1;
}

 

동시처리를 하려면

public async UniTaskVoid LoadManyAsync()
{
    //전부 완료되면 다음으로 넘어감
    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);
}

 

UniTask는 TemtMeshPro, DOTween, Addressables를 지원합니다.

// 순서대로 처리
await transform.DOMoveX(2, 10);
await transform.DOMoveZ(5, 20);

// 취소처리와 동시처리
var ct = this.GetCancellationTokenOnDestroy();

await UniTask.WhenAll(
    transform.DOMoveX(10, 3).WithCancellation(ct),
    transform.DOScale(10, 3).WithCancellation(ct));

 

코루틴을 사용하다 보면 여러 단점들이 있는데

예외처리가 불가능함

리턴 값이 따로 없음

사용 시 가비지 생성 (YieldInstruction는 캐싱해서 사용하면 되는데 StartCoroutine는 유니티 내부 코드라서 따로 최적화 불가능)

코루틴을 사용하게 되면 선형적인 코드 작성 방식에서 벗어나기 때문에 빠른 코드 해석이 어려워짐

오브젝트가 꺼지면 자동으로 코루틴이 꺼지는 부분

 

이러한 단점을 커버할 수 있는 게 바로 UniTask이라고 생각합니다.

 

UniTask는 충분히 코루틴을 대체할 수 있다고 생각합니다.