> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PlayFab Unified SDK에서 비동기 호출 수행

> PlayFab Unified SDK에서 XAsyncBlock과 XTaskQueueHandle을 사용하여 Core 및 Services 전반에 걸쳐 비동기 작업과 완료 콜백을 실행할 스레드를 제어합니다.

비동기 API는 빠르게 반환되지만 비동기 작업을 시작하고 작업이 완료된 후 결과가 반환되는 API입니다.

전통적으로, 게임은 완료 콜백을 사용할 때 어떤 스레드가 비동기 작업을 실행하고 어떤 스레드가 결과를 반환하는지 거의 제어하지 못했습니다. 일부 게임은 스레드 동기화의 필요성을 피하기 위해 힙의 한 섹션이 단일 스레드에서만 액세스되도록 설계되었습니다. 완료 콜백이 게임이 제어하는 스레드에서 호출되지 않으면 비동기 작업의 결과로 공유 상태를 업데이트하려면 스레드 동기화가 필요합니다.

PlayFab Unified SDK는 비동기 API 호출을 수행할 때 개발자에게 직접적인 스레드 제어를 제공하는 비동기 C API를 노출합니다.

다음은 **PFProfilesGetProfileAsync**를 호출하는 기본 예제입니다.

```cpp theme={null}
    XAsyncBlock* asyncBlock = new XAsyncBlock();
    asyncBlock->queue = GlobalState()->queue;
    asyncBlock->context = nullptr;
    asyncBlock->callback = [](XAsyncBlock* asyncBlock)
    {
        std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // take ownership of XAsyncBlock
        
        size_t bufferSize;
        HRESULT hr = PFProfilesGetProfileGetResultSize(asyncBlock, &bufferSize);
        if (SUCCEEDED(hr))
        {
            std::vector<char> getProfileResultBuffer(bufferSize);
            PFProfilesGetEntityProfileResponse* getProfileResponseResult{ nullptr };
            PFProfilesGetProfileGetResult(asyncBlock, getProfileResultBuffer.size(), getProfileResultBuffer.data(), &getProfileResponseResult, nullptr);
        }
    };

    PFProfilesGetEntityProfileRequest profileRequest{};
    HRESULT hr = PFProfilesGetProfileAsync(GlobalState()->entityHandle, &profileRequest, asyncBlock);
```

이 호출 패턴을 이해하려면 **XAsyncBlock** 및 **XTaskQueueHandle**을 사용하는 방법을 이해해야 합니다.

**XAsyncBlock**은 비동기 작업 및 완료 콜백과 관련된 모든 정보를 전달합니다.

**XTaskQueueHandle**을 사용하면 어떤 스레드가 비동기 작업을 실행하고 어떤 스레드가 **XAsyncBlock**의 완료 콜백을 호출할지 결정할 수 있습니다.

## XAsyncBlock

**XAsyncBlock**을 자세히 살펴보겠습니다. 다음과 같이 정의된 구조체입니다.

```cpp theme={null}
typedef struct XAsyncBlock
{
    /// <summary>
    /// The queue to queue the call on
    /// </summary>
    XTaskQueueHandle queue;

    /// <summary>
    /// Optional context pointer to pass to the callback
    /// </summary>
    void* context;

    /// <summary>
    /// Optional callback that will be invoked when the call completes
    /// </summary>
    XAsyncCompletionRoutine* callback;

    /// <summary>
    /// Internal use only
    /// </summary>
    unsigned char internal[sizeof(void*) * 4];
};
```

**XAsyncBlock**에는 다음이 포함됩니다.

* *queue* - 작업을 실행할 위치에 대한 정보를 나타내는 핸들인 **XTaskQueueHandle**. 이 매개변수가 설정되지 않으면 기본 큐가 사용됩니다.
* *context* - 콜백 함수에 데이터를 전달할 수 있습니다.
* *callback* - 비동기 작업이 완료된 후 호출될 선택적 콜백 함수. 콜백을 지정하지 않으면 **XAsyncGetStatus**로 **XAsyncBlock**이 완료될 때까지 기다린 후 결과를 얻을 수 있습니다.

각 비동기 호출에 대해 힙에 새 **XAsyncBlock**을 만들어야 합니다. **XAsyncBlock**은 **XAsyncBlock**의 완료 콜백이 호출될 때까지 유지되어야 하며 그 후에 삭제할 수 있습니다.

> 중요:
>
> **XAsyncBlock**은 비동기 작업이 완료될 때까지 메모리에 남아 있어야 합니다. 동적으로 할당된 경우 **XAsyncBlock**의 완료 콜백 내부에서 삭제할 수 있습니다.

### 비동기 작업 대기

비동기 작업이 완료되었는지 알 수 있는 두 가지 방법이 있습니다.

* **XAsyncBlock**의 완료 콜백이 호출됩니다.
* **XAsyncGetStatus**를 true로 호출하여 완료될 때까지 기다립니다.

**XAsyncGetStatus**를 사용하면 **XAsyncBlock**의 완료 콜백이 실행된 후 비동기 작업이 완료된 것으로 간주됩니다. 그러나 **XAsyncBlock**의 완료 콜백은 선택 사항입니다.

비동기 작업이 완료되면 결과를 얻을 수 있습니다.

### 비동기 작업 결과 가져오기

결과를 얻기 위해, 대부분의 비동기 API 함수에는 비동기 호출의 결과를 받기 위한 해당 Result 함수가 있습니다.

예제 코드에서 **PFProfilesGetProfileAsync**에는 해당 **PFProfilesGetProfileGetResult** 함수가 있습니다. 이 함수를 사용하여 함수의 결과를 검색하고 그에 따라 조치를 취할 수 있습니다.

결과 검색에 대한 자세한 내용은 각 비동기 API 함수의 설명서를 참조하세요.

## XTaskQueueHandle

**XTaskQueueHandle**을 사용하면 어떤 스레드가 비동기 작업을 실행하고 어떤 스레드가 **XAsyncBlock**의 완료 콜백을 호출할지 결정할 수 있습니다.

디스패치 모드를 설정하여 이러한 작업을 수행하는 스레드를 제어할 수 있습니다. 사용 가능한 세 가지 디스패치 모드가 있습니다.

* *Manual* - 수동 큐는 자동으로 디스패치되지 않습니다. 개발자가 원하는 스레드에서 디스패치하는 것은 개발자의 책임입니다. 이를 사용하여 비동기 호출의 작업 또는 콜백 측을 특정 스레드에 할당할 수 있습니다.
* *Thread Pool* - 스레드 풀을 사용하여 디스패치합니다. 스레드 풀은 스레드 풀 스레드가 사용 가능해짐에 따라 큐에서 호출을 가져와 병렬로 호출을 호출합니다. *Thread Pool*은 사용하기 가장 쉽지만 사용되는 스레드에 대한 제어가 가장 적습니다.
* *Serialized Thread Pool* - 스레드 풀을 사용하여 디스패치합니다. 스레드 풀은 단일 스레드 풀 스레드가 사용 가능해짐에 따라 큐에서 호출을 가져와 직렬로 호출을 호출합니다.
* *Immediate* - 큐에 대기 중인 작업을 제출한 스레드에서 즉시 디스패치합니다.

새 **XTaskQueueHandle**을 만들려면 **XTaskQueueCreate**를 호출해야 합니다. 예:

```cpp theme={null}
STDAPI XTaskQueueCreate(
    _In_ XTaskQueueDispatchMode workDispatchMode,
    _In_ XTaskQueueDispatchMode completionDispatchMode,
    _Out_ XTaskQueueHandle* queue
    ) noexcept;
```

이 함수는 두 개의 **XTaskQueueDispatchMode** 매개변수를 사용합니다. **XTaskQueueDispatchMode**에는 세 가지 가능한 값이 있습니다.

```cpp theme={null}
/// <summary>
/// Describes how task queue callbacks are processed.
/// </summary>
enum class XTaskQueueDispatchMode : uint32_t
{
    /// <summary>
    /// Callbacks are invoked manually by XTaskQueueDispatch
    /// </summary>
    Manual,

    /// <summary>
    /// Callbacks are queued to the system thread pool and will
    /// be processed in order by the thread pool across multiple thread
    /// pool threads.
    /// </summary>
    ThreadPool,
    
    /// <summary>
    /// Callbacks are queued to the system thread pool and
    /// will be processed one at a time.
    /// </summary>
    SerializedThreadPool,
    
    /// <summary>
    /// Callbacks are not queued at all but are dispatched
    /// immediately by the thread that submits them.
    /// </summary>
    Immediate
};
```

**workDispatchMode**는 비동기 작업을 처리하는 스레드의 디스패치 모드를 결정합니다. **completionDispatchMode**는 비동기 작업 완료를 처리하는 스레드의 디스패치 모드를 결정합니다.

**XTaskQueueHandle**을 만든 후에는 작업 및 완료 함수에 대한 스레딩을 제어하기 위해 **XAsyncBlock**에 추가하기만 하면 됩니다. **XTaskQueueHandle**을 다 사용했을 때, 일반적으로 게임이 끝날 때, **XTaskQueueCloseHandle**로 닫을 수 있습니다.

```cpp theme={null}
STDAPI_(void) XTaskQueueCloseHandle(
    _In_ XTaskQueueHandle queue
    ) noexcept;
```

호출 샘플:

```cpp theme={null}
XTaskQueueCloseHandle(queue);
```

### XTaskQueueHandle 수동으로 디스패치

**XTaskQueueHandle**의 작업 또는 완료 큐에 수동 큐 디스패치 모드를 사용한 경우 수동으로 디스패치해야 합니다. 작업 큐와 완료 큐가 모두 수동 디스패치로 설정된 **XTaskQueueHandle**이 다음과 같이 만들어졌다고 가정해 봅시다.

```cpp theme={null}
XTaskQueueHandle queue = nullptr;
HRESULT hr = XTaskQueueCreate(
    XTaskQueueDispatchMode::Manual,
    XTaskQueueDispatchMode::Manual,
    &queue);
```

**XTaskQueueDispatchMode::Manual**로 할당된 작업을 디스패치하려면 **XTaskQueueDispatch** 함수를 호출합니다.

```cpp theme={null}
STDAPI_(bool) XTaskQueueDispatch(
    _In_ XTaskQueueHandle queue,
    _In_ XTaskQueuePort port,
    _In_ uint32_t timeoutInMs
    ) noexcept;
```

호출 샘플:

```cpp theme={null}
HRESULT hr = XTaskQueueDispatch(queue, XTaskQueuePort::Completion, 0);
```

* *queue* - 작업을 디스패치할 큐.
* *port* - **XTaskQueuePort** 열거형의 인스턴스.
* *timeoutInMs* - 밀리초 단위의 타임아웃에 대한 uint32\_t.

**XTaskQueuePort** 열거형에서 정의된 두 가지 콜백 유형이 있습니다.

```cpp theme={null}
/// <summary>
/// Declares which port of a task queue to dispatch or submit
/// callbacks to.
/// </summary>
enum class XTaskQueuePort : uint32_t
{
    /// <summary>
    /// Work callbacks
    /// </summary>
    Work,

    /// <summary>
    /// Completion callbacks after work is done
    /// </summary>
    Completion
};
```

### XTaskQueueDispatch를 호출해야 할 때

큐가 새 항목을 받았을 때를 확인하기 위해, **XTaskQueueRegisterMonitor**를 호출하여 이벤트 처리기를 설정하여 작업 또는 완료가 디스패치될 준비가 되었음을 코드에 알릴 수 있습니다.

```cpp theme={null}
STDAPI XTaskQueueRegisterMonitor(
    _In_ XTaskQueueHandle queue,
    _In_opt_ void* callbackContext,
    _In_ XTaskQueueMonitorCallback* callback,
    _Out_ XTaskQueueRegistrationToken* token
    ) noexcept;
```

**XTaskQueueRegisterMonitor**는 다음 매개변수를 사용합니다.

* *queue* - 콜백을 제출하는 비동기 큐.
* *callbackContext* - 제출 콜백에 전달되어야 하는 데이터에 대한 포인터.
* *callback* - 새 콜백이 큐에 제출될 때 호출되는 함수.
* *token* - 콜백을 제거하기 위해 나중에 **XTaskQueueUnregisterMonitor**를 호출할 때 사용되는 토큰.

예를 들어, 다음은 **XTaskQueueRegisterMonitor**에 대한 호출입니다.

```cpp theme={null}
XTaskQueueRegisterMonitor(queue, nullptr, HandleAsyncQueueCallback, &m_callbackToken);
```

해당하는 **XTaskQueueMonitorCallback** 콜백은 다음과 같이 구현될 수 있습니다.

```cpp theme={null}
void CALLBACK HandleAsyncQueueCallback(
    _In_opt_ void* context,
    _In_ XTaskQueueHandle queue,
    _In_ XTaskQueuePort port)
{
    switch (port)
    {
    case XTaskQueuePort::Work:
        {
            std::lock_guard<std::mutex> lock(g_workReadyMutex);
            g_workReady = true;
        }

        g_workReadyConditionVariable.notify_one(); // (std::condition_variable)
        break;
    }
}
```

그런 다음 백그라운드 스레드에서 이 조건 변수가 깨어날 때까지 수신 대기하고 **XTaskQueueDispatch**를 호출할 수 있습니다.

```cpp theme={null}
void BackgroundWorkThreadProc(XTaskQueueHandle queue)
{
    while (true)
    {
        {
            std::unique_lock<std::mutex> cvLock(g_workReadyMutex);
            g_workReadyConditionVariable.wait(cvLock, [] { return g_workReady; });

            if (g_stopBackgroundWork)
            {
                break;
            }

            g_workReady = false;
        }

        bool workFound = false;
        do
        {
            workFound = XTaskQueueDispatch(queue, XTaskQueuePort::Work, 0);
        } while (workFound);
    }
    
    XTaskQueueCloseHandle(queue);
}
```

## 참고

* [비동기 작업](/services/playfab/sdks/unified-sdk/async-model)
* [메모리 관리](/services/playfab/sdks/unified-sdk/memory-management)
* [추적 및 진단](/services/playfab/sdks/unified-sdk/debug-trace)


## Related topics

- [PlayFab Unified SDK에서 메모리 관리](/ko/services/playfab/sdks/unified-sdk/memory-management.md)
- [PlayFab Unified SDK에서 디버그 추적](/ko/services/playfab/sdks/unified-sdk/debug-trace.md)
- [PlayFab 독립 실행형 SDK v1에서 Unified SDK v2로 마이그레이션](/ko/services/playfab/sdks/unified-sdk/migrating-from-v1.md)
- [비동기 호출 만들기](/ko/services/playfab/sdks/c/async.md)
- [XSAPI C API에서 비동기 호출 수행](/ko/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
