카테고리 없음

유니티 - 비동기 프로그래밍에 대해 알아보자. #4 (Awaitable)

근본넘치는개발자 2024. 11. 19. 22:06

 

2024.11.18 - [분류 전체보기] - 유니티 - 비동기 프로그래밍에 대해 알아보자. #3 (UniTask)

 

 

오늘은 awaitable을 정리하는 시간을 가지고자 합니다.

 

 

Awaitable

Awaitable 타입은 유니티에서 Task를 대체하여 비동기 작업을 표현하는 타입으로,

유니티가 추적할 수 있는 타입입니다.

Task와 비교하여 유니티 개발 환경에 최적화되어 설계되었다고 합니다.

 

 

특징

내부 풀링 시스템으로 메모리 할당 최소화

=> async 메서드를 실행할 때마다 Task 타입을 위한 메모리 할당이 발생하는 문제를 해결

=>  동일 인스턴스 중복 await이 불가능하다.

 

GC 압박감소

 

 

 

주요 메서드 

 

NextFrameAsync(): 다음 프레임 대기

WaitForSecondsAsync(): 시간 대기

EndOfFrameAsync(): 프레임 끝 대기

 

AwaitableCompletionSource로 수동 완료 처리해야 한다.

 

 

UniTask와 Awaitable 비교

UniTask에서 이야기했던 비교 내용을 가져와 정리해 봤습니다.

원문을 읽어보고 싶으신 분이라면 아래 링크에서 awaitable 내용을 찾아보시면 됩니다.

 

https://github.com/Cysharp/UniTask

 

 

 

정리 :

UniTask와 비교하여

 

프레임 기반 작업 제한적

PlayerLoopTiming(= Unity의 실행 루프(Update loop) 내의 특정 타이밍 ) 제어 기능 제한

Tracker Window 없음

 

 

결론:

UniTask가 Awaitable의 상위 집합이므로,

특별한 제약이 없다면 UniTask 사용이 더 유리함

 

 

외부 종속성 회피 필요시 Awaitable 사용

그 외에는 UniTask 권장

 

 

Task와 Awaitable 비교

 

아래 표는 참고 자료 중 유니티 매뉴얼에 들어가면 Task와 awaitable을 비교하는 원본 내용을 보실 수 있습니다.

 

특징                    | Task                      | Awaitable
----------------------|---------------------------|---------------------------
다중 await             | 가능                       | 불가능 (풀링으로 인해)
값 반환                 | Task<T> 지원              | Awaitable<T> 지원
완료 트리거             | TaskCompletionSource     | AwaitableCompletionSource
연속성 실행             | 비동기(ThreadPool 사용)    | 동기(즉시 실행)

 

 

결론 :

Unity 게임 개발에서는 성능을 위해 Awaitable 사용이 권장되나, 중복 await가 필요한 경우 Task 사용 고려

 

예시 코드 

 

더보기
using UnityEngine;
using System;

public class AwaitableExample : MonoBehaviour
{
    // 1. 기본적인 Awaitable 사용
    async void BasicExample()
    {
        // 다음 프레임까지 대기
        await Awaitable.NextFrameAsync();
        
        // 지정된 시간만큼 대기
        await Awaitable.WaitForSecondsAsync(2.0f);
        
        // EndOfFrame까지 대기
        await Awaitable.EndOfFrameAsync();
    }

    // 2. 값을 반환하는 Awaitable 사용
    async void ReturnValueExample()
    {
        Awaitable<int> calculation = CalculateAsync();
        int result = await calculation;
        Debug.Log($"계산 결과: {result}");
    }

    async Awaitable<int> CalculateAsync()
    {
        await Awaitable.NextFrameAsync();
        return 42;
    }

    // 3. AwaitableCompletionSource 사용
    async void CompletionSourceExample()
    {
        var completionSource = new AwaitableCompletionSource<string>();
        
        // 다른 스레드나 이벤트에서 완료 처리
        CompleteAsync(completionSource);
        
        // 결과 대기
        string result = await completionSource.Awaitable;
        Debug.Log(result);
    }

    async void CompleteAsync(AwaitableCompletionSource<string> source)
    {
        await Awaitable.NextFrameAsync();
        source.SetResult("완료!");
    }

    // 4. 취소 토큰 사용
    async void CancellationExample()
    {
        var cancellationToken = this.GetCancellationTokenOnDestroy();
        
        try
        {
            await Awaitable.WaitForSecondsAsync(5.0f, cancellationToken);
            Debug.Log("완료!");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("작업이 취소됨");
        }
    }

    // 5. 풀링된 Awaitable 사용 시 주의 사항
    async void PoolingWarningExample()
    {
        // 올바른 사용
        await Awaitable.NextFrameAsync();
        await Awaitable.NextFrameAsync();

        // 잘못된 사용 - 동일 인스턴스 재사용
        var awaitable = Awaitable.NextFrameAsync();
        await awaitable;
        // await awaitable; // 예외 발생!
    }

    // 6. 연속성 실행 예제
    async void ContinuationExample()
    {
        Debug.Log("시작");
        
        await Awaitable.NextFrameAsync();
        Debug.Log("동기적으로 즉시 실행됨");
        
        await Task.Yield();
        Debug.Log("비동기적으로 실행됨 (ThreadPool)");
    }

    // 7. 실제 사용 사례
    async void LoadResourceExample()
    {
        try
        {
            // 리소스 로딩
            var request = Resources.LoadAsync<GameObject>("Prefab");
            await request;

            if (request.asset != null)
            {
                var go = Instantiate(request.asset as GameObject);
                await Awaitable.WaitForSecondsAsync(1.0f);
                Destroy(go);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"리소스 로딩 실패: {e.Message}");
        }
    }

    // 8. 여러 Awaitable 조합
    async void CombineAwaitablesExample()
    {
        var task1 = DoSomethingAsync();
        var task2 = DoSomethingElseAsync();

        await task1;
        await task2;
        
        Debug.Log("모든 작업 완료");
    }

    async Awaitable DoSomethingAsync()
    {
        await Awaitable.WaitForSecondsAsync(1.0f);
    }

    async Awaitable DoSomethingElseAsync()
    {
        await Awaitable.WaitForSecondsAsync(2.0f);
    }
}

 

마무리

 

오늘은 간단하게 awaitable에 대해 정리해 봤습니다.

awaitable이 뭔지 호기심에서 시작한 건데 참 멀리도 돌아왔네요 ㅋ;

 

자료를 정리하면서 보니까 비교 부분에서도 설명했지만

큰 이유 없으면 웬만해선 다들 Unitask로 진행하는 것 같습니다.

 

참고한 자료

 

https://youtu.be/2fupgczJn9g?si=ip5ppV9wdt0R642D

 

https://docs.unity3d.com/kr/2023.2/Manual/AwaitSupport.html

 

Await 지원 - Unity 매뉴얼

Unity 2023.1에서는 C# async 및 await 키워드를 사용하여 간소화된 비동기 프로그래밍 모델을 지원합니다. Unity의 비동기 API는 대부분 다음을 포함하는 async/await 패턴을 지원합니다.

docs.unity3d.com

https://docs.unity3d.com/kr/2023.2/ScriptReference/Awaitable.html

 

Awaitable - Unity 스크립팅 API

Awaitable type used to expose asynchronous code, as well as an async return type specifically tailored for Unity.

docs.unity3d.com

https://www.unitysquare.co.kr/growwith/unityblog/webinarView?id=566&utm_source=facebook-page&utm_medium=social&utm_campaign=kr_Unity6Blog_Awaitable

 

Unity Square

Unity 6 Awaitable로 깔끔한 비동기 프로그래밍 구현하기

unitysquare.co.kr