기술 자료

Unity Engine

Unity Engine

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

Understand the key features of Unity's Awaitable and how it compares to both .NET Task and iterator-based coroutines.
읽는 시간 4분최근 업데이트: 한 달 전

Awaitable
클래스는 대기가 가능하며 C# 비동기 프로그래밍 모델에서 비동기 반환 유형으로 사용할 수 있는 커스텀 Unity 유형입니다. Unity의 대부분의 비동기 API는 다음을 포함하는
async
및
await
패턴을 지원합니다.
다음과 같이 자체 코드에서
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
Task
및
ValueTask
간의 기능을 비교한 내용을 요약한 것입니다.

기능

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>
를 사용하면 됩니다.
WaitAll
및
WaitAny
에 대한 빌트인 지원
예.아니오.
ValueTask.AsTask
를 통해
Task
로 전환해야 합니다.
아니오.
커스텀
AsTask
확장 메서드를 통해
Task
로 전환해야 합니다. 코드 예시 레퍼런스에서 .NET 작업에서 Awatiable 래핑을 참조하십시오.
Unity 스레드 및 업데이트 루프 인식 실행 작업 예약아니오.아니오.예.
Awaitable.BackgroundThreadAsync
및
Awaitable.MainThreadAsync
를 통해
Awaitable
가 재개되는 스레드를 지정할 수 있습니다.
Awaitable.NextFrameAsync
및
Awaitable.FixedUpdateAsync
를 통해
Update
또는
FixedUpdate
루프를 기준으로 작업을 예약할 수도 있습니다. 자세한 내용은 Awaitable 완료 및 지속을 참조하십시오.

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

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

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

Awaitable
코루틴은 일반적으로 반복자 기반 코루틴보다 더 효율적이며, 특히
WaitForFixedUpdate
와 같이 반복자가 null이 아닌 값을 반환하는 경우에 더욱 효율적입니다.
그러나 여러 개의
Awaitable
코루틴을 동시에 실행하면 성능상의 이점이 줄어듭니다. 예를 들어 이전의 코드 예시에서
while
루프에서
Awaitable.NextFrameAsync
를 기다리는 MonoBehavior는 대규모 프로젝트에서 모든 게임 오브젝트에 연결될 경우 성능 문제를 일으킬 가능성이 높습니다.

추가 리소스