> ## 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.

# 비동기 호출 만들기

> 스레드 제어를 위해 XAsyncBlock, XTaskQueueHandle 및 디스패치 모드를 사용하여 PlayFab Services C/C++ SDK에서 비동기 호출을 수행합니다.

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

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

PlayFab Services SDK는 [**PFAuthenticationLoginWithCustomIDAsync**](/services/playfab/api-references/c/pfauthentication/functions/pfauthenticationloginwithcustomidasync), [**PFDataGetFilesAsync**](/services/playfab/api-references/c/pfdata/functions/pfdatagetfilesasync) 또는 [**PFProfilesGetProfileAsync**](/services/playfab/api-references/c/pfprofiles/functions/pfprofilesgetprofileasync)와 같은 비동기 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**](/services/playfab/api-references/c/pfprofiles/functions/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);
}
```

## 참조

[API 참조 문서](/services/playfab/api-references/c/pfauthentication/pfauthentication_members)


## Related topics

- [XSAPI C API에서 비동기 호출 수행](/ko/services/xbox-services/fundamentals/xbox-services-api/live-flatc-async-patterns.md)
- [PlayFab Unified SDK에서 비동기 호출 수행](/ko/services/playfab/sdks/unified-sdk/async-model.md)
- [방법: 복합 작업 큐 만들기](/ko/build/core-features/common/async/async-task-queue-design-howto/creating-composite-task-queue.md)
- [Lobby SDK 빠른 시작](/ko/services/playfab/multiplayer/lobby/lobby-getting-started.md)
- [비동기 작업 큐 설계](/ko/build/core-features/common/async/async-task-queue-design.md)
