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

# XSAPI C API에서 비동기 호출 수행

> XAsyncBlock 및 XTaskQueueHandle을 사용하여 XBOX Live 타이틀에서 XblProfileGetUserProfileAsync와 같은 비동기 XSAPI C 호출에 대한 스레딩을 제어합니다.

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

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

XSAPI C API는 개발자가 **XblSocialGetSocialRelationshipsAsync()**, **XblProfileGetUserProfileAsync()** 및 \*\*XblAchievementsGetAchievementsForTitleIdAsync()\*\*와 같은 **비동기 API** 호출을 할 때 직접 스레드 제어를 제공하는 새로운 비동기 C API를 노출합니다.

다음은 **XblProfileGetUserProfileAsync** API를 호출하는 기본 예제입니다:

```cpp theme={null}
 XAsyncBlock* asyncBlock = new XAsyncBlock();
    asyncBlock->queue = GlobalState()->queue;
    asyncBlock->context = nullptr;
    asyncBlock->callback = [](XAsyncBlock* asyncBlock)
    {
        XblUserProfile profile = { 0 };
        HRESULT hr = XblProfileGetUserProfileResult(asyncBlock, &profile);
        delete asyncBlock;
    };

    HRESULT hr = XblProfileGetUserProfileAsync(GlobalState()->xboxLiveContext, GlobalState()->xboxUserId, 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**이 완료될 때까지 기다린 다음 결과를 가져올 수 있습니다.

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

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

### **비동기 작업** 기다리기

**비동기 작업**이 완료되었는지 여러 가지 방법으로 확인할 수 있습니다:

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

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

**비동기 작업**이 완료되면 결과를 가져올 수 있습니다.

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

결과를 가져오려면 대부분의 **비동기 API** 함수에 비동기 호출의 결과를 수신하는 해당 \[함수 이름]Result 함수가 있습니다.

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

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

## **XTaskQueueHandle**

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

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

* *Manual* - 수동 큐는 자동으로 디스패치되지 않습니다. 개발자가 원하는 스레드에서 디스패치해야 합니다. 이는 비동기 호출의 작업 또는 콜백 측을 특정 스레드에 할당하는 데 사용할 수 있습니다. 자세한 내용은 아래에서 설명합니다.

* *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**에 대한 호출입니다:

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

## 참고 항목

[XSAPI C API 소개](/services/xbox-services/fundamentals/xbox-services-api/live-xsapi-flat-c)

[XSAPI 참조](/reference/live/xsapi-c/atoc-xsapi-c)

[libHttpClient](/reference/live/httpclient/atoc-httpclient)


## Related topics

- [XBOX services API](/ko/services/xbox-services/fundamentals/xbox-services-api/index.md)
- [PlayFab Unified SDK에서 비동기 호출 수행](/ko/services/playfab/sdks/unified-sdk/async-model.md)
- [비동기 호출 만들기](/ko/services/playfab/sdks/c/async.md)
- [비동기 프로그래밍 모델](/ko/build/core-features/common/async/async-programming-model.md)
- [XAsyncProvider function](/ko/reference/system/xasyncprovider/functions/xasyncprovider.md)
