이 레퍼런스의 예시들은 비동기 코드를 작성할 때 발생하는 일반적인 시나리오에서 사용할 수 있는 Awaitable 솔루션을 보여줍니다.
Unity의 테스트 프레임워크는 Awaitable을 유효한 테스트 반환 유형으로 인식하지 않습니다. 그러나 다음 예시는 IEnumerator의 Awaitable 구현을 사용하여 비동기 테스트를 작성하는 방법을 보여줍니다.
[UnityTest]
public IEnumerator SomeAsyncTest(){
async Awaitable TestImplementation(){
// test something with async / await support here
};
return TestImplementation();
}
Awaitable 클래스에서 프레임 관련 비동기 메서드를 사용하여 반복자 기반 코루틴을 대체하는 비동기 Unity 코루틴을 생성할 수 있습니다.
async Awaitable SampleSchedulingJobsForNextFrame()
{
// Wait until end of frame to avoid competing over resources with other Unity subsystems
await Awaitable.EndOfFrameAsync();
var jobHandle = ScheduleSomethingWithJobSystem();
// Let the job execute while the next frame starts
await Awaitable.NextFrameAsync();
jobHandle.Complete();
// Use results of computation
}
JobHandle ScheduleSomethingWithJobSystem()
{
...
}
반복자 기반 코루틴에서 WaitUntil은 델리게이트가 true를 평가할 때까지 코루틴 실행을 일시 중지합니다. 취소 토큰을 사용하여 조건이 변경될 때까지 기다리게 함으로써 Awaitable 반환 비동기 메서드와 동등한 동작을 만들 수 있습니다.
public static async AwaitableUntil(Func<bool> condition, CancellationTokenSource cancellationToken)
{
while(!condition()){
cancellationToken.ThrowIfCancellationRequested();
await Awaitable.NextFrameAsync();
}
}
이후에 다음과 같이 취소 토큰을 전달할 수 있습니다.
cancellationTokenSource = new CancellationTokenSource();
currentTask = AwaitableUntil(myCondition, cancellationTokenSource.Token);
메인 스레드를 차단하지 않도록 비동기식 리소스 로드 작업을 await할 수 있습니다.
public async Awaitable Start()
{
// Load texture resource asynchronously
var operation = Resources.LoadAsync("my-texture");
// Return control to the main thread while the resource loads
await operation;
var texture = operation.asset as Texture2D;
}
동일한 방법으로 여러 개의 서로 다른 await 호환 유형을 await할 수 있습니다.
public async Awaitable Start()
{
await CallSomeThirdPartyAPIReturningDotnetTask();
await Awaitable.NextFrameAsync();
await SceneManager.LoadSceneAsync("my-scene");
await SomeUserCodeReturningAwaitable();
...
}
.NET Task 래핑하여 Awaitable의 몇 가지 제한 사항을 우회할 수 있습니다. 이렇게 하면 할당 비용이 발생하지만 Task API의 WhenAll 및 WhenAny와 같은 메서드에 액세스할 수 있게 됩니다. 이를 위해서는 다음과 같이 커스텀 AsTask 확장 메서드를 자체적으로 작성할 수 있습니다.
// Implement custom AsTask extension methods to wrap Awaitable in Task
public static class AwaitableExtensions
{
public static async Task AsTask(this Awaitable a)
{
await a;
}
public static async Task<T> AsTask<T>(this Awaitable<T> a)
{
return await a;
}
}
Awaitable과 Task의 주요 차이점은 할당을 줄이기 위해 Awaitable 객체를 풀링하는 것입니다. 결과를 여러 번 완료하는 Awaitable 반환 메서드를 안전하게 await할 수는 없습니다. 이는 원본 Awaitable 오브젝트는 반환된 이후에 풀로 돌아가기 때문입니다.
다음 코드는 안전하지 않으며 예외와 멈춤 현상을 발생시킵니다.
async Awaitable Bar(){
var taskWithResult = SomeAwaitableReturningFunction();
var awaitOnce = await taskWithResult;
// Do something
// The following will cause errors because at this point taskWithResult has already been pooled back
var awaitTwice = await taskWithResult;
}
이는 할당 비용을 감수하면서 Awaitable을 Task에 래핑할 수 있는 시나리오에 해당합니다. 이를 통해 Task를 여러 번 안전하게 기다릴 수 있습니다.
// Implement custom AsTask extension methods to wrap Awaitable in Task
public static class AwaitableExtensions
{
public static async Task AsTask(this Awaitable a)
{
await a;
}
public static async Task<T> AsTask<T>(this Awaitable<T> a)
{
return await a;
}
}
async Awaitable Bar(){
var taskWithResult = SomeAwaitableReturningFunction();
// Wrap the returned Awaitable in a Task
var taskWithResultAsTask = taskWithResult.AsTask();
// The task can now be safely awaited multiple times, at the cost of allocating
var awaitOnce = await taskWithResultAsTask;
// Do something
var awaitTwice = await taskWithResultAsTask;
}