Awaitable 작업 완료 및 지속
Understand how asynchronous code resumes on completion of an awaited task and how this affects the function and performance of your application.
읽는 시간 4분최근 업데이트: 한 달 전
awaitTaskAwaitable.NET Task 지속
대기 시작 시 상태 코드에 대한 정보를 동기화 컨텍스트라고 합니다. .NET 플랫폼은 이러한 유형의 정보를 캡처할 수 있도록 클래스를 제공합니다. 지속은 비동기 메서드가 호출된 동기화 컨텍스트에서 실행되며, 동기화 컨텍스트가 설정되지 않았을 때는 스레드 풀을 통해 실행됩니다.
SynchronizationContextTask대부분의 Unity API는 스레드 세이프가 아니며 메인 스레드에서만 호출할 수 있습니다. 이러한 이유로 Unity는 기본 를 커스텀 로 덮어 써서, 편집 모드와 재생 모드의 모든 .NET 지속이 기본적으로 메인 스레드에서 실행되게 합니다. Unity 메인 스레드에서 반환 메서드를 호출하면, 지속이 에 게시되며 메인 스레드의 다음 프레임 틱에서 실행됩니다. 이를 백그라운드 스레드에서 호출하면 스레드 풀 스레드에서 완료됩니다.
SynchronizationContextUnitySynchronizationContextTaskTaskUnitySynchronizationContextUpdate동기화 컨텍스트를 캡처하면 애플리케이션의 성능 오버헤드가 증가하며, 메인 스레드에서 다음 프레임 업데이트가 재개될 때까지 기다리면서 대규모 지연이 발생합니다. 대신 을 사용하여 두 가지 문제를 모두 피할 수 있습니다.
AwaitableAwaitable 지속
달리 명시되지 않는 한, Unity API에서 반환된 모든 인스턴스는 메인 스레드에서 완료되므로 동기화 컨텍스트를 캡처할 필요가 없습니다. 지속은 작업이 완료되면 동기적으로 실행되므로 코드가 즉시 재개될 수 있으며 다음 프레임을 기다릴 필요가 없습니다.
AwaitableAwaitable기본 지속 동작을 를 통해 명시적으로 오버라이드할 수 있습니다. 다음 예시는 이 배경 스레드에서 완료되도록 지정합니다.
Awaitable.BackgroundThreadAsyncAwaitableprivate async Awaitable<float> DoHeavyComputationInBackgroundAsync(){ await Awaitable.BackgroundThreadAsync(); // do some heavy math here return 42; // this will make the returned awaitable complete on a background thread}public async Awaitable Start(){ var computationResult = await DoHeavyComputationInBackgroundAsync(); await SceneManager.LoadSceneAsync("my-scene"); // this will fail as SceneManager.LoadAsync only works from main thread}
이 경우 백그라운드 스레드에서 을 완료하도록 설정하면 문제가 발생합니다. 백그라운드 스레드에서 메서드가 재개되면 는 메인 스레드에서만 작동하므로 다음 호출이 실패합니다.
AwaitableStartSceneManager.LoadSceneAsyncSceneManager.LoadSceneAsync이러한 상황을 바로 잡으려면 를 사용하여 반환 메서드가 기본적으로 메인 스레드에서 완료되게 하면서 백그라운드 스레드 작업 옵션을 여전히 제공할 수 있습니다. 다음 예시에서 는 기본적으로 메인 스레드에서 작업을 완료하기 전에 백그라운드 스레드에서 일부 작업을 수행합니다. 파라미터는 대규모 컴퓨팅 작업을 체이닝할 때와 같이, 적절한 경우에 백그라운드 스레드에서 완료할 수 있는 옵션을 호출자에게 제공합니다.
Awaitable.MainThreadAsyncAwaitableDoHeavyComputationInBackgroundAsynccontinueOnMainThreadprivate async Awaitable<float> DoHeavyComputationInBackgroundAsync(bool continueOnMainThread = true){ await Awaitable.BackgroundThreadAsync(); // do some heavy math here float result = 42; // by default, switch back to main thread: if(continueOnMainThread){ await Awaitable.MainThreadAsync(); } return result;}public async Awaitable Start(){ var computationResult = await DoHeavyComputationInBackgroundAsync(); await SceneManager.LoadSceneAsync("my-scene"); // this is ok!}
참고: Unity는 플레이 모드가 종료되더라도 백그라운드에서 실행되는 코드를 자동으로 중지하지 않습니다. 플레이 모드를 종료할 때 백그라운드 작업이 취소되도록 하려면 을 사용하십시오.
Application.exitCancellationToken스레드 전환 및 성능
메인 스레드에서 를 호출하고 백그라운드 스레드에서 를 호출하는 것이 가장 효율적입니다. 이는 각 경우에 코드가 완료되면 즉시 재개되기 때문입니다. 가 있는 백그라운드 스레드에서 메인 스레드로 다시 전환할 경우, 메인 스레드의 다음 프레임 업데이트까지 코드를 재개할 수 없습니다.
await Awaitable.MainThreadAsync()await Awaitable.BackgroundThreadAsync()MainThreadAsync메인 스레드에서 호출한 반환 API가 동기식으로 완료되지 않을 경우, 적어도 다음 틱(30fps에서 33ms)까지 기다려야 지속 실행이 시작됩니다. 네트워크 지연이 문제일 경우 메인 스레드에서 이 작업을 수행하고 커스텀 로직을 사용하여 메인 스레드와 네트워킹 작업을 동기화하는 것이 좋습니다.
TaskUpdate개발 빌드에서 멀티 스레드 코드로 Unity API를 사용하려고 하는 경우 Unity는 다음과 같은 오류 메시지를 표시합니다.
UnityException: Internal_CreateGameObject can only be called from the main thread.Constructors and field initializers will be executed from the loading thread when loading a scene.Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
중요: 성능상의 이유로 Unity는 비개발 빌드에서 멀티스레드 동작을 확인하지 않으며 라이브 빌드에서는 이 오류를 표시하지 않습니다. Unity는 이러한 상황에서 멀티스레드 코드 실행을 막지는 않지만, 여러 스레드를 사용할 경우 크래시 및 기타 예측할 수 없는 오류가 발생할 수 있습니다. 자체 멀티스레딩을 사용하는 대신 Unity의 잡 시스템을 사용하는 것이 더 안전합니다. 잡 시스템은 여러 스레드를 안전하게 사용해 여러 잡을 병렬적으로 실행하여 멀티스레딩의 성능상 이점을 달성합니다. 자세한 내용은 잡 시스템 개요를 참조하십시오.
Awaitable과 잡 시스템 비교
아래 시나리오에는 잡 시스템보다 Unity의 클래스가 더 적합합니다.
Awaitable- 파일 조작이나 웹 요청 수행과 같이 본질적으로 비동기적인 작업을 비차단 방식으로 처리할 때 코드를 단순화합니다.
- 장기 실행 작업(1프레임 이상)을 백그라운드 스레드로 오프로드합니다.
- 반복자 기반 코루틴을 최신화합니다.
- 여러 종류의 비동기 작업(프레임 이벤트, Unity 이벤트, 타사 비동기 API, I/O)을 기다립니다.
하지만 컴퓨팅 집약적인 알고리즘을 병렬화하는 등, 짧은 길이의 작업에는 적합하지 않습니다. 멀티 코어 CPU를 최대한 활용하고 알고리즘을 병렬화하려면 잡 시스템을 대신 사용하십시오.
코드에서 완료를 트리거
AwaitableCompletionSourceAwaitableCompletionSource<T>Awaitablepublic class UserNamePrompt : MonoBehavior { TextField _userNameTextField; AwaitableCompletionSource<string> _completionSource = new AwaitableCompletionSource<string>(); public void Start() { var rootVisual = GetComponent<UIDocument>().rootVisualElement; var userNameField = rootVisual.Q<TextField>("userNameField"); rootVisual.Q<Button>("OkButton").clicked += ()=>{ _completionSource.SetResult(userNameField.text); } } public Awaitable<string> WaitForUsernameAsync() => _completionSource.Awaitable;}...public class HighScoreRanks : MonoBehavior { ... public async Awaitable ReportCurrentUserScoreAsync(int score) { _userNameOverlayGameObject.SetActive(true); var prompt = _userNameOverlayGameObject.GetComponent<UserNamePrompt>(); var userName = await prompt.WaitForUsernameAsync(); _userNameOverlayGameObject.SetActive(false); await SomeAPICall(userName, score); }}