Version: Unity 6.0 (6000.0)
언어 : 한국어
Awaitable 클래스를 통한 비동기 프로그래밍
Awaitable 작업 완료 및 지속

Awaitable을 통한 비동기 프로그래밍 소개

Awaitable 클래스는 대기가 가능하며 C# 비동기 프로그래밍 모델에서 비동기 반환 유형으로 사용할 수 있는 커스텀 Unity 유형입니다. Unity의 대부분의 비동기 API는 다음을 포함하는 asyncawait 패턴을 지원합니다.

다음과 같이 자체 코드에서 Awaitable 클래스를 await 연산자와 함께 사용하거나 async 반환 유형으로 사용할 수 있습니다.

async Awaitable<List<Achievement>> GetAchievementsAsync()
{
    var apiResult = await SomeMethodReturningATask(); // or any await-compatible type
    List<Achievement> achievements = JsonConvert.DeserializeObject<List<Achievement>>(apiResult);
    return achievements;
}

async Awaitable ShowAchievementsView()
{
    ShowLoadingOverlay();
    List<Achievement> achievements = await GetAchievementsAsync();
    HideLoadingOverlay();
    ShowAchivementsList(achievements);
}

Awaitable과 .NET Task 비교

Awaitable는 Unity 프로젝트의 비동기 코드에 대해서 .NET Task보다 효율적인 대안을 제공하도록 설계되었습니다. Awaitable의 효율성은 Task에 비해 몇 가지 중요한 한계가 있습니다.

가장 중요한 제한 사항은 할당을 제한하기 위해 Awaitable 인스턴스를 풀링한다는 것입니다. 다음 예시를 참고하십시오.

class SomeMonoBehaviorWithAwaitable : MonoBehavior
{
    public async void Start()
    {
        while(true)
        {
            // do some work on each frame
            await Awaitable.NextFrameAsync();
        }
    }
}

풀링이 없다면 이 예시에서 MonoBehavior의 각 인스턴스는 매 프레임마다 Awaitable 오브젝트를 할당하게 되므로 가비지 컬렉터 작업 부하가 증가하고 성능이 저하됩니다. Unity는 이를 완화하기 위해 Awaitable 오브젝트가 대기 처리된 후 내부 Awaitable 풀로 반환합니다.

중요: Awaitable 인스턴스를 풀링한다는 것은 Awaitable 인스턴스에서 두 번 이상 await를 수행하는 것은 안전하지 않다는 의미입니다. 그렇게 하면 예외나 교착 상태와 같이 정의되지 않은 행동이 발생할 수 있습니다.

Awaitable과 .NET ValueTask 비교

.NET ValueTask<TResult>Awaitable의 몇 가지 주요 이점과 한계를 제공합니다. ValueTask는 일반적으로 대부분의 경우 동기식으로 완료될 것으로 예상되는 비동기식 워크로드에 사용하는 것이 좋습니다. 자세한 내용은 ValueTask의 이유, 내용, 시점 이해하기를 참고하십시오.

Awaitable, Task, ValueTask 요약

아래의 표는 Unity의 Awaitable 클래스와 .NET TaskValueTask 간의 기능을 비교한 내용을 요약한 것입니다.

기능 Task ValueTask UnityEngine.Awaitable
필수 할당 많음.
모든 호출에 Task 반환 메서드를 할당하여 메모리 사용량과 가비지 컬렉터 작업량을 증가시킵니다.
필요한 만큼.
풀링으로 최적화할 수 있습니다.
필요에 따라 최소한.
기본적으로 Awaitable 인스턴스가 풀링되기 때문에 Awaitable 반환 메서드를 호출하는 것은 일반적으로 메모리를 할당하지 않습니다.
여러 번 안전하게 대기 가능 . 아니오.
ValueTask.AsTask를 사용해 Task로 전환해야 합니다.
아니오.
커스텀 AsTask 확장 메서드가 있는 Task로 전환해야 합니다. 코드 예시 레퍼런스의 동일한 메서드에서 여러 번 대기를 참조하십시오.
비동기식으로 실행되는 지속 .
기본적으로 동기화 컨텍스트를 사용하며, 그렇지 않으면 ThreadPool을 사용합니다. 이는 코드가 다음 프레임 Update를 재개할 때까지 기다려야 하기 때문에 Unity의 메인 스레드를 완료할 때 지연 시간이 증가합니다.
.
대기 중인 작업들이 동기식으로 완료되는 경우에 최적화되었습니다. 작업들이 비동기식으로 완료될 경우 지속 동작은 Task와 동일합니다.
아니오.
지속은 완료가 트리거되면 동기식으로 실행됩니다. 즉, 완료가 트리거된 해당 프레임에서 코드가 즉시 재개됩니다. 자세한 내용은 Awaitable 완료 및 지속에서 확인하십시오.
코드로 완료를 트리거할 수 있음 .
TaskCompletionSource를 사용하면 됩니다.
대부분 동기식으로 완료되는 작업의 일반적인 사용 사례에는 적용되지 않습니다. .
AwaitableCompletionSource를 사용하면 됩니다.
값 반환 가능 .
Task<TResult>를 사용하면 됩니다.
.
ValueTask<TResult>를 사용하면 됩니다.
.
UnityEngine.Awaitable<T>를 사용하면 됩니다.
WaitAllWaitAny에 대한 빌트인 지원 . 아니오.
ValueTask.AsTask를 통해 Task로 전환해야 합니다.
아니오.
커스텀 AsTask 확장 메서드를 통해 Task로 전환해야 합니다. 코드 예시 레퍼런스에서 .NET 작업에서 Awatiable 래핑을 참조하십시오.
Unity 스레드 및 업데이트 루프 인식 실행 작업 예약 아니오. 아니오. .
Awaitable.BackgroundThreadAsyncAwaitable.MainThreadAsync를 통해 Awaitable가 재개되는 스레드를 지정할 수 있습니다. Awaitable.NextFrameAsyncAwaitable.FixedUpdateAsync를 통해 Update 또는 FixedUpdate 루프를 기준으로 작업을 예약할 수도 있습니다. 자세한 내용은 Awaitable 완료 및 지속을 참조하십시오.

Task 또는 ValueTask가 아닌 Awaitable을 사용해야 하는 경우

API 선택 기준은 비동기 코드의 성능 프로파일에 따라 달라지지만 일반적으로는 다음과 같습니다.

  • 여러 번 또는 여러 소비자로에서 동시에 대기해야 할 경우에는 Task만 사용할 수 있습니다.
  • 대부분의 경우 동기적으로 완료되는 높은 처리량의 비동기식 코드가 있을 경우 ValueTask를 사용하는 것이 좋습니다.
  • Awaitable은 다음과 같은 경우에 적합합니다.
    • 메서드를 여러 번 기다릴 필요가 없으며 대부분 비동기식으로 완료될 것으로 예상되는 경우
    • 비동기 작업이 메인 스레드와 UpdateFixedUpdate 루프와 같은 Unity 관련 개념을 기본적으로 지원하기를 원합니다.

Awaitable과 반복자 기반 코루틴 비교

Awaitable 코루틴은 일반적으로 반복자 기반 코루틴보다 더 효율적이며, 특히 WaitForFixedUpdate와 같이 반복자가 null이 아닌 값을 반환하는 경우에 더욱 효율적입니다.

그러나 여러 개의 Awaitable 코루틴을 동시에 실행하면 성능상의 이점이 줄어듭니다. 예를 들어 이전의 코드 예시에서 while 루프에서 Awaitable.NextFrameAsync를 기다리는 MonoBehavior는 대규모 프로젝트에서 모든 게임 오브젝트에 연결될 경우 성능 문제를 일으킬 가능성이 높습니다.

추가 리소스

Awaitable 클래스를 통한 비동기 프로그래밍
Awaitable 작업 완료 및 지속