UniTask 알아보기

jinwook4217·2020년 12월 17일
4
post-thumbnail

Cysharp/UniTask

UniTask 는 유니티용 async-await 통합 패키지이다. C# 의 Task, async-await 를 유니티에 맞게 더 최적화 하고, 유니티에서 사용하기 편하도록 더 많은 기능을 추가한 느낌이다.

UniTask(사용자 지정 작업과 유사한 개체)가 필요한 이유는 무엇입니까?

작업이 너무 무거우므로 Unity 스레딩(단일 스레드)과 일치하지 않습니다. Unity의 거의 비동기 객체가 Unity의 엔진 계층에 의해 자동으로 전송되므로 UniTask는 스레드 및 SynchronizationContext/ExecutionContext를 사용하지 않습니다. Unity와 완벽하게 통합되어 더 빠르고 더 적은 할당량을 획득합니다.

UniTask는 세 가지 확장 방법을 제공합니다.

* await asyncOperation;
* .WithCancellation(CancellationToken);
* .ToUniTask(IProgress, PlayerLoopTiming, CancellationToken);

Basic Examples


기본적으로 많이 사용될 것 같은 예제 위주로 가져왔습니다. 모든 예제와 더 자세한 내용을 확인하려면 깃헙 페이지에서 확인하세요.

Unity's AsyncObject

var asset = await Resources.LoadAsync<TextAsset>("foo");

await SceneManager.LoadSceneAsync("scene2");

Delay

// 100 Frames
await UniTask.DelayFrame(100);

// 1000 ms
await UniTask.Delay(1000);

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

WaitUntil

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

// special helper of WaitUntil
await UniTask.WaitUntilValueChanged(this, x => x.isActive);

IEnumerator coroutine

// You can await IEnumerator coroutine
await FooCoroutineEnumerator();

Standard Task

// You can await standard task
await Task.Run(() => 100);

WebRequest

// get async webrequest
async UniTask<string> GetTextAsync(UnityWebRequest req) {
    var op = await req.SendWebRequest();
    return op.downloadHandler.text;
}

var task1 = GetTextAsync(UnityWebRequest.Get("http://google.com"));

UniTask.WhenAll & UniTask.WhenAny

public async UniTaskVoid LoadManyAsync() {
    // parallel load.
    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);
}

Timeout

// You can handle timeout easily
await GetTextAsync(UnityWebRequest.Get("http://unity.com"))
		.Timeout(TimeSpan.FromMilliseconds(300));

Cancellation and Exception handling


await-async 를 사용할 때 가장 기본적인 사용은 await 를 호출하는 가장 최상단에서 try-catch 로 로직을 감싸는 것입니다.

public void OnClickGetTextFromGoogle() {
    GetTextFromGoogleAsync().Forget();
}

private async UniTaskVoid GetTextFromGoogleAsync() {
    var uri = "http://google.com";
    try {
        var result = await GetTextAsync(uri);
        Debug.Log(result);
    }
    catch (Exception e) {
        Debug.LogException(e);
    }
}

private async UniTask<string> GetTextAsync(string uri) {
    var uwr = UnityWebRequest.Get(uri);

    await uwr.SendWebRequest();

    if (uwr.isHttpError || uwr.isNetworkError) {
        // 실패시 예외 throw
        throw new Exception(uwr.error);
    }

    return uwr.downloadHandler.text;
}

비동기 작업 호출시 취소 토큰을 인자로 같이 보내 진행중인 비동기 작업을 취소할 수 있다.

// 취소 버튼을 눌렀을 때 진행한 비동기 작업 취소하기
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);

만약 async UniTask 함수 안에서 작업을 취소하고 싶다면, OperationCanceledException 에러를 던지세요.

public async UniTask<int> FooAsync() {
    await UniTask.Yield();
    throw new OperationCanceledException();
}

만약 exception 을 핸들링 하는데, 작업 취소 에러는 핸들링 하고 싶지 않을 때

public async UniTask<int> BarAsync() {
    try {
        var x = await FooAsync();
        return x * 2;
    }
    catch (Exception ex) when (!(ex is OperationCanceledException)) {
        return -1;
    

async void vs async UniTaskVoid


async void 는 사용하지 않는 편이 좋다. async UniTaskVoidasync UniTask 보다 더 가볍다. 만약 await 할 필요가 없다면 async UniTaskVoid 를 사용하는 것이 좋다. 만약 경고를 무시하려면 Forget() 를 사용한다.

public void OnClickLoadSprite() {
    LoadSpriteAsync("hello").Forget();
}

public async UniTaskVoid LoadSpriteAsync(string path) {
    var resource = await Resources.LoadAsync<Sprite>(path);
    spriteRender.sprite = resource as Sprite;
}

예시

public void OnClickDelaySecond() {
    Debug.Log("delay start!");
    Delay(1000).Forget();
    Debug.Log("delay end!");
}

public async UniTaskVoid Delay(int ms) {
    await UniTask.Delay(ms);
    Debug.Log($"delay {ms} ms!");
}

// 실행 결과
// delay start!
// delay end!
// ... 1초 후
// delay 1000 ms!

람다식에서 async 를 사용하면 기본적으로 async void 를 사용하게 된다. 이를 피하기 위해 UniTask.Action 또는 UniTask.UnityAction 을 통해 async UniTaskVoid 대리자를 만들 수 있다.

Action actEvent;
UnityAction unityEvent; // especially used in uGUI

// Bad: async void
actEvent += async () => { };
unityEvent += async () => { };

// Ok: create Action delegate by lambda
actEvent += UniTask.Action(async () => { await UniTask.Yield(); });
unityEvent += UniTask.UnityAction(async () => { await UniTask.Yield(); });

External Assets


UniTask 는 TextMeshPro, uGUI, DOTween, Addressables 의 비동기 처리도 지원한다. 자세한 내용은 공식 깃헙 페이지에서 확인. 당연히 UniRx 와 같이 사용 가능

profile
유니티 개발을 조금씩 해왔습니다.

0개의 댓글