# Awaitable 작업 완료 및 지속

> Understand how asynchronous code resumes on completion of an awaited task and how this affects the function and performance of your application.

`await` 연산자는 둘러싸는 비동기 메서드의 실행을 일시 중단하여 호출 스레드가 대기하는 동안 다른 작업을 수행할 수 있게 합니다. 대기 중인 `Task` 또는 `Awaitable`이 완료되면 비동기 코드를 중단된 지점에서 다시 실행해야 합니다. 비동기 코드를 재시작하는 방식은 애플리케이션의 기능과 성능에 중대한 영향을 미칠 수 있습니다.

## .NET Task 지속##.net-task-continuations

대기 시작 시 상태 코드에 대한 정보를 동기화 컨텍스트라고 합니다. .NET 플랫폼은 이러한 유형의 정보를 캡처할 수 있도록 [`SynchronizationContext`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext?view=net-8.0) 클래스를 제공합니다. `Task` 지속은 비동기 메서드가 호출된 동기화 컨텍스트에서 실행되며, 동기화 컨텍스트가 설정되지 않았을 때는 [스레드 풀](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?view=net-8.0)을 통해 실행됩니다.

대부분의 Unity API는 스레드 세이프가 아니며 메인 스레드에서만 호출할 수 있습니다. 이러한 이유로 Unity는 기본 [`SynchronizationContext`](https://docs.microsoft.com/en-us/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext)를 커스텀 [`UnitySynchronizationContext`](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/UnitySynchronizationContext.cs)로 덮어 써서, 편집 모드와 재생 모드의 모든 .NET `Task` 지속이 기본적으로 메인 스레드에서 실행되게 합니다. Unity 메인 스레드에서 `Task` 반환 메서드를 호출하면, 지속이 `UnitySynchronizationContext`에 게시되며 메인 스레드의 다음 프레임 `Update` 틱에서 실행됩니다. 이를 백그라운드 스레드에서 호출하면 스레드 풀 스레드에서 완료됩니다.

동기화 컨텍스트를 캡처하면 애플리케이션의 성능 오버헤드가 증가하며, 메인 스레드에서 다음 프레임 업데이트가 재개될 때까지 기다리면서 대규모 지연이 발생합니다. 대신 `Awaitable`을 사용하여 두 가지 문제를 모두 피할 수 있습니다.

## Awaitable 지속##awaitable-continuations

달리 명시되지 않는 한, Unity API에서 반환된 모든 `Awaitable` 인스턴스는 메인 스레드에서 완료되므로 동기화 컨텍스트를 캡처할 필요가 없습니다. `Awaitable` 지속은 작업이 완료되면 동기적으로 실행되므로 코드가 즉시 재개될 수 있으며 다음 프레임을 기다릴 필요가 없습니다.

기본 지속 동작을 [`Awaitable.BackgroundThreadAsync`](/engine/6000.0/script-reference/unity-engine/awaitable/background-thread-async.md)를 통해 명시적으로 오버라이드할 수 있습니다. 다음 예시는 `Awaitable`이 배경 스레드에서 완료되도록 지정합니다.

```lang-csharp
private 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
}
```

이 경우 백그라운드 스레드에서 `Awaitable`을 완료하도록 설정하면 문제가 발생합니다. 백그라운드 스레드에서 `Start` 메서드가 재개되면 `SceneManager.LoadSceneAsync`는 메인 스레드에서만 작동하므로 다음 `SceneManager.LoadSceneAsync` 호출이 실패합니다.

이러한 상황을 바로 잡으려면 [`Awaitable.MainThreadAsync`](/engine/6000.0/script-reference/unity-engine/awaitable/main-thread-async.md)를 사용하여 `Awaitable` 반환 메서드가 기본적으로 메인 스레드에서 완료되게 하면서 백그라운드 스레드 작업 옵션을 여전히 제공할 수 있습니다. 다음 예시에서 `DoHeavyComputationInBackgroundAsync`는 기본적으로 메인 스레드에서 작업을 완료하기 전에 백그라운드 스레드에서 일부 작업을 수행합니다. `continueOnMainThread` 파라미터는 대규모 컴퓨팅 작업을 체이닝할 때와 같이, 적절한 경우에 백그라운드 스레드에서 완료할 수 있는 옵션을 호출자에게 제공합니다.

```lang-csharp
private 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`](/engine/6000.0/script-reference/unity-engine/application/exit-cancellation-token.md)을 사용하십시오.

## 스레드 전환 및 성능##thread-switching-and-performance

메인 스레드에서 `await Awaitable.MainThreadAsync()`를 호출하고 백그라운드 스레드에서 `await Awaitable.BackgroundThreadAsync()`를 호출하는 것이 가장 효율적입니다. 이는 각 경우에 코드가 완료되면 즉시 재개되기 때문입니다. `MainThreadAsync`가 있는 백그라운드 스레드에서 메인 스레드로 다시 전환할 경우, 메인 스레드의 다음 [프레임 업데이트](/engine/6000.0/manual/scripting/object-oriented-development/managing-time-and-frame-rate/time-per-frame-updates.md)까지 코드를 재개할 수 없습니다.

메인 스레드에서 호출한 `Task` 반환 API가 동기식으로 완료되지 않을 경우, 적어도 다음 `Update` 틱(30fps에서 33ms)까지 기다려야 지속 실행이 시작됩니다. 네트워크 지연이 문제일 경우 메인 스레드에서 이 작업을 수행하고 커스텀 로직을 사용하여 메인 스레드와 네트워킹 작업을 동기화하는 것이 좋습니다.

개발 빌드에서 멀티 스레드 코드로 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는 [비개발 빌드](/engine/6000.0/manual/unity-editor/editor-features/build-settings/build-profiles-reference.md)에서 멀티스레드 동작을 확인하지 않으며 라이브 빌드에서는 이 오류를 표시하지 않습니다. Unity는 이러한 상황에서 멀티스레드 코드 실행을 막지는 않지만, 여러 스레드를 사용할 경우 크래시 및 기타 예측할 수 없는 오류가 발생할 수 있습니다. 자체 멀티스레딩을 사용하는 대신 Unity의 [잡 시스템](/engine/6000.0/manual/scripting/optimization/job-system.md)을 사용하는 것이 더 안전합니다. 잡 시스템은 여러 스레드를 안전하게 사용해 여러 잡을 병렬적으로 실행하여 멀티스레딩의 성능상 이점을 달성합니다. 자세한 내용은 [잡 시스템 개요](/engine/6000.0/manual/scripting/optimization/job-system/overview.md)를 참조하십시오.

### Awaitable과 잡 시스템 비교##awaitable-compared-to-the-job-system

아래 시나리오에는 잡 시스템보다 Unity의 `Awaitable` 클래스가 더 적합합니다.

* 파일 조작이나 웹 요청 수행과 같이 본질적으로 비동기적인 작업을 비차단 방식으로 처리할 때 코드를 단순화합니다.
* 장기 실행 작업(1프레임 이상)을 백그라운드 스레드로 오프로드합니다.
* 반복자 기반 코루틴을 최신화합니다.
* 여러 종류의 비동기 작업(프레임 이벤트, Unity 이벤트, 타사 비동기 API, I/O)을 기다립니다.

하지만 컴퓨팅 집약적인 알고리즘을 병렬화하는 등, 짧은 길이의 작업에는 적합하지 않습니다. 멀티 코어 CPU를 최대한 활용하고 알고리즘을 병렬화하려면 [잡 시스템](/engine/6000.0/manual/scripting/optimization/job-system.md)을 대신 사용하십시오.

## 코드에서 완료를 트리거##triggering-completion-from-code

[`AwaitableCompletionSource`](/engine/6000.0/script-reference/unity-engine/awaitable-completion-source.md) 및 [`AwaitableCompletionSource<T>`](/engine/6000.0/script-reference/unity-engine/awaitable-completion-source-1.md)로 사용자 코드에서 완료가 발생하는 `Awaitable` 인스턴스를 생성할 수 있습니다. 예를 들어 사용자 상호작용이 끝날 때까지 상태 머신 구현하지 않고서도 사용자 프롬프트를 구현하는 데 사용할 수 있습니다.

```lang-csharp

public 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);
    }
}
```

## 추가 리소스##additional-resources

* [Awaitable 코드 예시 레퍼런스](/engine/6000.0/manual/scripting/optimization/async-await-support/async-awaitable-examples.md)
