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

# 비동기 프로그래밍 모델

> 비동기 프로그래밍 모델

Microsoft Game Development Kit(GDK)는 XBOX One ERA 프로그래밍 모델의 일부로 구현된 비동기 패턴과 관련하여 게임 개발자로부터 받은 피드백을 반영한 새로운 비동기 API 패턴을 구현합니다. 우리의 목표는 이 새로운 패턴이 일반적인 게임 아키텍처에 통합하기 훨씬 쉽고, 게임 개발자가 요청한 높은 수준의 제어를 제공하는 것입니다. 이 항목에서는 해당 설계 패턴을 설명하고, 비동기 패턴을 구현하는 데 사용할 수 있는 라이브러리에 대한 제안을 제공합니다.

<a id="gdk_asynchronous_model" />

## 개념 모델

Microsoft Game Development Kit(GDK)의 비동기 프로그래밍은 크게 두 가지 구성 요소로 나뉩니다. 작업(Task)과 작업 큐(Task Queue)입니다. 라이브러리에는 더 많은 기능이 있지만, 전체 개념 모델은 이 두 가지 주요 구성 요소를 활용합니다.

작업은 시작, 상태 확인, 취소 가능, 완료, 완료 정보 반환이 가능한 단일 비동기 작업 세트입니다. Microsoft Game Development Kit(GDK) 모델에서 작업은 두 개의 본문으로 구성됩니다. 즉, 작업 콜백과 완료 콜백입니다. 이를 통해 완전 병렬 처리나 병렬 작업과 단일 스레드 완료를 결합한 방식과 같은 더 많은 제어가 가능합니다.

작업 큐는 나중에 실행하기 위해 작업 콜백과 완료 콜백을 모두 큐에 넣는 컨테이너입니다. 작업 큐에는 포트라고 하는 두 개의 내부 큐가 있으며 작업 콜백과 완료 콜백을 각각 별도로 처리합니다. 이를 각각 작업 포트와 완료 포트라고 합니다.

**그림 1. 작업과 작업 큐 다이어그램**

<img src="https://mintcdn.com/microsoft-4404708b/sIrPFR_ir_sNKVEv/images/gdk/features/common/async_task_taskqueue_diagram.png?fit=max&auto=format&n=sIrPFR_ir_sNKVEv&q=85&s=89181466bdb096dbacd570ff0f1dcac5" alt="작업과 작업 큐의 다이어그램." width="660" height="314" data-path="images/gdk/features/common/async_task_taskqueue_diagram.png" />

작업 큐의 각 포트는 [만들 때](/reference/system/xtaskqueue/functions/xtaskqueuecreate) 서로 다른 콜백 실행 동작을 만들도록 다르게 구성됩니다. 예를 들어 작업 포트는 비동기로 구성하고, 완료 포트는 주 스레드에서 직렬로 실행되도록 구성할 수 있습니다. 실행 동작을 완전히 제어할 수 있도록 수동 설정을 지정할 수도 있습니다. 포트 구성 모드는 [아래에서 설명합니다](#controlling_work_dispatching).

비동기 작업을 시작할 때 콜백이 즉시 작업 큐에 큐잉되지 않습니다. [비동기 공급자](/build/core-features/common/async/async-libraries/async-library-xasyncprovider)가 상태 변경을 처리하여 완료 콜백이 큐에 들어가고 디스패치되기 전에 작업이 큐에 들어가고 디스패치되도록 보장합니다.

작업 큐 자체는 스레딩을 직접 처리하지 않습니다. 대신 포트를 [디스패치](/reference/system/xtaskqueue/functions/xtaskqueuedispatch)하기 위한 외부 호출에 의존합니다. 외부 호출은 스레딩 및 동시성 동작을 결정합니다. 작업 큐 자체는 완전히 스레드로부터 안전합니다.

**그림 2. 여러 스레드로 디스패치되는 포트**

<img src="https://mintcdn.com/microsoft-4404708b/sIrPFR_ir_sNKVEv/images/gdk/features/common/async_port_dispatch_multithread.png?fit=max&auto=format&n=sIrPFR_ir_sNKVEv&q=85&s=697aae48e97cb3aa23845108060a1882" alt="여러 스레드로 디스패치되는 포트를 보여 주는 이미지." width="628" height="230" data-path="images/gdk/features/common/async_port_dispatch_multithread.png" />

기본적으로 이것이 전부입니다! 작업의 콜백은 작업 큐의 작업 포트와 완료 포트에 큐잉되며, 해당 작업 큐는 이러한 콜백을 어떤 방식으로 디스패치합니다. API에는 작업 큐 관리, 콜백 상태 확인, 작업 데이터 추적, 사용자 지정 작업 처리 만들기 등의 전체 기능 모음이 포함되어 있습니다.

Microsoft Game Development Kit(GDK) 비동기 API 호출은 항상 내부적으로 작업 콜백을 구현하며, 완료 콜백은 항상 선택 사항입니다. Microsoft Game Development Kit(GDK) 비동기 호출 이외의 용도로 사용하는 경우, 사용자가 작업 콜백을 제공해야 합니다.

## 요구 사항

게임 개발자는 API 호출에 대한 다음과 같은 요구 사항을 나열했습니다.

1. 비동기 호출보다 동기 호출을 선호합니다.
2. 폴링이 있는 비동기를 제공합니다.
3. 콜백이 있는 비동기를 제공합니다.
4. 비동기 작업이 실행되는 스레드에 대한 제어를 제공합니다.
5. 완료 콜백이 실행되는 스레드에 대한 제어를 제공합니다.

## API 유형

Microsoft Game Development Kit(GDK)는 API 설계에서 매우 직관적이 되려고 노력합니다. 게임 개발자는 하드웨어를 최대한 활용하기 위해 코드를 미세 조정하는 전문가입니다. 우리는 가능할 때마다 그들에게 제어권을 부여합니다. API 구현은 다음과 같은 유형으로 분류됩니다.

* **시간에 민감하며 안전한(Time Sensitive Safe):** 시간에 민감하며 안전한 API는 시간에 민감한 스레드에서 호출할 수 있는 API입니다. 이는 일반적으로 API가 사소하거나 매우 빠르다는 것을 의미하지만, 핵심 개념은 API의 성능 특성이 *일관성*을 유지한다는 것입니다. 이러한 API는 항상 동기적이며 비동기 버전이 필요하지 않습니다. 이러한 API는 time-sensitive-safe로 문서화되어야 합니다.

* **시간에 민감하며 안전하지 않은(Not Time Sensitive Safe):** 이러한 API는 렌더 스레드에서 호출하기에 안전하지 않습니다. 성능 특성이 매우 다를 수 있습니다. 대부분의 API가 이 범주에 속합니다.

* **비동기(Asynchronous):** 이러한 API는 웹 서비스 호출과 같이 본질적으로 비동기적입니다. 이 항목에서 설명하는 비동기 패턴을 사용합니다. 비동기 API는 XBOX One ERA 프로그래밍 모델에서만큼 Microsoft Game Development Kit(GDK)에서는 흔하지 않습니다. 비동기 API는 일반적으로 오래 실행되며 취소 가능합니다. 몇 가지 특정 사용 사례를 제외하고 비동기 API는 시간이 중요하지 않은 안전한 동기 버전을 가집니다. 비동기 API 호출은 항상 시간이 중요한 상황에서 안전해야 합니다.

* **알림(Notifications):** 알림은 주기적인 성격을 가지며 정해진 종료 시점이 없습니다. 비동기 API와 관련이 있지만, 주기적인 특성으로 인해 개발자에게 다르게 보이고 동작해야 합니다. 알림 등록은 항상 시간이 중요한 상황에서 안전해야 합니다.

## 비동기 API 패턴

Microsoft Game Development Kit(GDK)는 Microsoft Game Development Kit(GDK) 구성 요소가 일관된 비동기 지원을 제공하는 데 사용할 수 있는 범용 비동기 API 패턴을 소개합니다. 핵심에는 OVERLAPPED와 유사한 구조인 [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)이 있습니다.

```c++ theme={null}
typedef void CALLBACK XAsyncCompletionRoutine(struct XAsyncBlock* asyncBlock);

struct XAsyncBlock
{
    XTaskQueueHandle queue;
    void* context;
    XAsyncCompletionRoutine* callback;
    unsigned char internal[sizeof(void*) * 4];
};
```

[XAsyncBlock](/reference/system/xasync/structs/xasyncblock)은 호출자가 제공하는 구조체입니다. 호출자는 다음 표에 표시된 대로 이 구조체의 선택적 필드를 채웁니다.

| 필드           | 설명                                                                                                                               |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **queue**    | 비동기 호출을 실행할 스레드를 제어할 수 있는 작업 큐 핸들입니다. 이 매개 변수가 null이면 프로세스 작업 큐가 사용됩니다. 프로세스 작업 큐가 null로 설정되어 있으면 호출이 E\_NO\_TASK\_QUEUE로 실패합니다. |
| **context**  | 콜백 함수에 전달되는 선택적 컨텍스트 포인터입니다.                                                                                                     |
| **callback** | 작업이 완료되었을 때 호출되는 선택적 콜백 함수입니다.                                                                                                   |

**Internal** 필드는 시스템이 사용하며 수정해서는 안 됩니다. 이 구조체의 사용자가 설정할 수 있는 필드는 비동기 작업 중에 수정해서는 안 됩니다. [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)은 비동기 작업의 수명 동안 메모리에 유지되어야 합니다. [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)이 동적으로 할당된 경우, 완료 콜백이 이를 삭제할 수 있는 가장 이른 시점입니다.

[XAsyncBlock](/reference/system/xasync/structs/xasyncblock) 외에도, 다음과 같이 소수의 도우미 API가 있습니다.

```c++ theme={null}
STDAPI XAsyncGetStatus(XAsyncBlock* asyncBlock, bool wait);

STDAPI XAsyncGetResultSize(XAsyncBlock* asyncBlock, size_t* bufferSize);

STDAPI_(void) XAsyncCancel(XAsyncBlock* asyncBlock);

typedef HRESULT CALLBACK XAsyncWork(XAsyncBlock* asyncBlock);

STDAPI XAsyncRun(XAsyncBlock* asyncBlock, XAsyncWork* work);
```

[XAsyncGetStatus](/reference/system/xasync/functions/xasyncgetstatus)는 비동기 호출의 상태를 반환합니다. 호출이 시작되면 이 상태는 E\_PENDING입니다. 완료되면 S\_OK 또는 특정 오류로 변경됩니다. 호출이 취소되면 E\_ABORT를 반환합니다.

[XAsyncGetResultSize](/reference/system/xasync/functions/xasyncgetresultsize)는 호출 결과를 가져오는 데 필요한 버퍼 크기를 반환합니다. 결과를 가져오는 실제 API는 각 비동기 호출에 맞게 조정됩니다.

[XAsyncCancel](/reference/system/xasync/functions/xasynccancel)을 사용하여 호출을 취소할 수 있습니다. 취소는 취소되는 작업의 몫이며 동기적, 비동기적으로 발생할 수도 있고 전혀 발생하지 않을 수도 있습니다. 작업이 취소되면 [XAsyncGetResult](/reference/system/xasyncprovider/functions/xasyncgetresult), [XAsyncGetResultSize](/reference/system/xasync/functions/xasyncgetresultsize) 또는 [XAsyncGetStatus](/reference/system/xasync/functions/xasyncgetstatus)가 E\_ABORT를 반환합니다. 취소된 호출은 [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)의 [XAsyncCompletionRoutine](/reference/system/xasync/functions/xasynccompletionroutine) 매개 변수를 신호로 알리고 콜백을 호출합니다.

[XAsyncRun](/reference/system/xasync/functions/xasyncrun)은 모든 코드를 비동기적으로 실행할 수 있는 도우미 메서드입니다.

### 비동기 API 사용

먼저 다음 코드 예제에서 동기 API를 살펴보겠습니다.

```c++ theme={null}
HRESULT XGameSaveGetRemainingQuota(XGameSaveProviderHandle provider,
int64_t* remainingQuota);
```

이 API는 웹 서비스를 호출하여 남은 게임 저장소 용량을 확인합니다. 비동기 지원을 추가하기 위해 다음과 같이 새로운 API 쌍을 선언합니다.

```c++ theme={null}
HRESULT XGameSaveGetRemainingQuotaAsync(XGameSaveProviderHandle
provider, XAsyncBlock* async);

HRESULT XGameSaveGetRemainingQuotaResult(XAsyncBlock* async,
int64_t* remainingQuota);
```

[XGameSaveGetRemainingQuotaAsync](/reference/system/xgamesave/functions/xgamesavegetremainingquotaasync)는 비동기 호출이 시작되면 S\_OK를 반환합니다(이 API는 비동기 전용이므로 E\_PENDING을 반환하는 것은 의미가 없기 때문). [XGameSaveGetRemainingQuotaResult](/reference/system/xgamesave/functions/xgamesavegetremainingquotaresult)는 호출이 완료될 때까지 E\_PENDING을 반환합니다.

이를 실제로 살펴보면 다음과 같습니다.

```c++ theme={null}
// providerHandle is a previously obtained XGameSaveProviderHandle.

XAsyncBlock* b = new XAsyncBlock;
ZeroMemory(b, sizeof(XAsyncBlock));
b->context = this;
b->queue = queue;
b->callback = [](XAsyncBlock* async)
{
    int64_t remainingQuota;
    if(SUCCEEDED(XGameSaveGetRemainingQuotaResult(async, &remainingQuota)))
    {
        printf("Remaining quota: %irn", remainingQuota);
    }
    delete async;
};
XGameSaveGetRemainingQuotaAsync(providerHandle, b);
```

[XAsyncBlocks](/reference/system/xasync/structs/xasyncblock)는 모두 작업 큐(다음 참조)를 필요로 하며, 이 큐는 비동기 호출이 실행되는 위치와 방법을 제어합니다. 아무 것도 제공되지 않으면 프로세스 전체 작업 큐가 사용됩니다.

[XAsyncBlock](/reference/system/xasync/structs/xasyncblock)은 비동기 호출의 수명 동안 메모리에 남아 있어야 합니다. 이 예에서는 동적으로 할당되고 완료 콜백에서 삭제되었습니다. 전역 변수 또는 멤버 변수로 저장할 수도 있습니다. 동일한 [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)이 한 번에 두 개 이상의 비동기 호출에 사용되면 정의되지 않은 동작이 발생합니다.

[XGameSaveGetRemainingQuotaResult](/reference/system/xgamesave/functions/xgamesavegetremainingquotaresult)는 비동기 호출 주기를 완료합니다. 이는 async 블록의 내부 데이터를 해제하므로, 이제 블록을 새 호출에 사용할 수 있습니다. [XGameSaveGetRemainingQuotaResult](/reference/system/xgamesave/functions/xgamesavegetremainingquotaresult)에 대한 후속 호출은 실패합니다. [XGameSaveGetRemainingQuotaAsync](/reference/system/xgamesave/functions/xgamesavegetremainingquotaasync)와 [XGameSaveGetRemainingQuotaResult](/reference/system/xgamesave/functions/xgamesavegetremainingquotaresult)도 async 블록 내에서 쌍을 이루므로, 하나의 비동기 호출을 다른 결과 API와 잘못 짝지으면 오류가 발생합니다.

비동기 호출에 데이터 페이로드가 없는 경우, 즉 HRESULT 상태만 중요한 경우 다음과 같이 async 블록만 받는 **Result** 메서드를 정의합니다.

```c++ theme={null}
HRESULT QueryUpdateStatusAsyncResult(_Inout_ XAsyncBlock* block);
```

<a id="controlling_work_dispatching" />

### 작업 디스패치 제어

이전 호출에서 비동기 작업은 어떤 스레드에서 수행되었을까요? 어떤 스레드가 완료 콜백을 호출했을까요? 이는 [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)에 할당된 작업 큐에 의해 결정됩니다.

작업 큐에는 두 개의 "포트"가 있습니다. *작업 포트*와 *완료 포트*입니다. 각 포트에는 포트에 큐잉된 콜백이 어떻게 처리되는지 결정하는 디스패치 모드가 있습니다. 여러 가지 디스패치 모드가 있습니다.

* **Thread pool(스레드 풀):** 스레드 풀 큐에 큐잉된 콜백은 시스템 스레드 풀에서 실행됩니다. 스레드 풀은 스레드 풀 스레드가 사용 가능해질 때마다 큐에서 호출을 하나씩 가져와 병렬로 호출합니다.

* **Serialized thread pool(직렬화된 스레드 풀):** 콜백이 큐잉되고 스레드 풀에서 실행되지만 한 번에 하나씩 실행됩니다.

* **Manual(수동):** 수동 큐에 큐잉된 콜백은 자동으로 디스패치되지 않습니다. 개발자가 원하는 스레드에서 디스패치해야 합니다.

* **Immediate(즉시):** 즉시 디스패치 모드는 큐잉을 전혀 하지 않습니다. 콜백을 제출한 스레드에서 즉시 호출을 실행합니다.

기본 프로세스 작업 큐가 구성되어 있어 작업 포트와 완료 포트가 모두 시스템 스레드 풀을 통해 디스패치됩니다. [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)에 큐 매개 변수가 전달되지 않으면 이 프로세스 작업 큐가 사용됩니다. 게임에서 프로세스 작업 큐를 비활성화하여 [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)에 큐를 전달하도록 요구할 수도 있습니다.

많은 개발자가 비동기 작업 및 완료 콜백이 실행되는 시기와 위치를 완전히 제어하기 위해 수동 디스패치 모드를 선택할 것으로 예상됩니다.

작업 큐에 대한 자세한 내용은 [비동기 작업 큐 설계](/build/core-features/common/async/async-task-queue-design)를 참조하세요.

## 알림

알림에는 종료 시점이 없을 수 있으며 여러 번 호출될 수 있습니다. 알림은 비동기 호출 요구 사항의 하위 집합을 지원해야 합니다.

1. 폴링이 있는 비동기
2. 콜백이 있는 비동기
3. 콜백이 발생하는 스레드에 대한 제어

알림은 개발자가 콜백 스레드를 제어할 수 있도록 작업 큐를 사용하지만, 그 외에는 async 블록을 사용하지 않습니다. **Register** 및 **Unregister** 메서드가 있는 표준 이벤트와 더 유사하게 보이도록 설계되었습니다.

* 호출별 매개 변수, 작업 큐, 선택적 void 컨텍스트, 강력한 형식의 콜백 포인터를 받는 **Register** 메서드입니다. 마지막 매개 변수는 토큰을 반환하는 *out* 매개 변수입니다.

* 호출별 컨텍스트와 토큰을 받는 **Unregister** 메서드입니다.

* 폴링은 알림 콜백과 관련이 없는 별도의 메서드를 추가하여 지원됩니다.

Windows 메시지를 가져올 수 있는 다음 예를 살펴보겠습니다.

```c++ theme={null}
struct XTaskQueueRegistrationToken;

typedef void MessageAvailableCallback(void* context, const MSG* msg);

HRESULT RegisterMessageAvailable(
    XTaskQueueHandle queue,
    void* context,
    MessageAvailableCallback* callback,
    XTaskQueueRegistrationToken * token);

bool UnregisterMessageAvailable(XTaskQueueRegistrationToken token, bool
wait);

// Usage.
XTaskQueueRegistrationToken token;
RegisterMessageAvailable(queue, nullptr, [](void*, const MSG* msg)
{
    printf("Message: %drn", msg->message);
}, &token);
```

이 예에서 **UnregisterMessageAvailable**은 마지막 "wait" 매개 변수를 받고 bool을 반환합니다. 이를 통해 호출자는 호출이 진행되고 있는 동안 등록 취소를 처리하는 방법을 결정할 수 있습니다.

<a id="heading-7" />

## 비동기 라이브러리

비동기 패턴을 지원하는 일관된 API를 더 쉽게 만들 수 있도록, API의 "async plumbing"을 구현하는 데 사용할 수 있는 라이브러리를 제공합니다. 라이브러리의 API는 다음과 같습니다.

```c++ theme={null}
enum class XAsyncOp : uint32_t
{
    Begin,
    DoWork,
    GetResult,
    Cancel,
    Cleanup
};

struct XAsyncProviderData
{
    XAsyncBlock* async;  
    size_t bufferSize;  
    void* buffer;  
    void* context;
};

typedef HRESULT CALLBACK XAsyncProvider(
_In_ XAsyncOp op,
_Inout_ XAsyncProviderData* data);

STDAPI XAsyncBegin (
_Inout_ XAsyncBlock* asyncBlock,
_In_opt_ void* context,
_In_opt_ void* identity,
_In_opt_ const char* identityName,
_In_ XAsyncProvider* provider);

STDAPI XAsyncSchedule(
_Inout_ XAsyncBlock* asyncBlock,
_In_ uint32_t delayInMs);

STDAPI_(void) XAsyncComplete(
_Inout_ XAsyncBlock* asyncBlock,
_In_ HRESULT result,
_In_ size_t requiredBufferSize);

STDAPI XAsyncGetResult(
_Inout_ XAsyncBlock* asyncBlock,
_In_opt_ void* identity,
_In_ size_t bufferSize,
_Out_writes_bytes_opt_(bufferSize) void* buffer,
_Out_opt_ size_t* bufferUsed);
```

이 API는 API가 호출되는 이유를 나타내는 작업 값과 함께 단일 콜백을 사용합니다. 호출이 진행됨에 따라 채워지는 단일 데이터 구조도 있습니다. 이 API를 사용하려면 다음을 수행합니다.

1. 호출자가 전달한 async 블록으로 [XAsyncBegin](/reference/system/xasyncprovider/functions/xasyncbegin)을 호출하고, 구현을 제공하는 콜백을 제공합니다.

2. 호출에 대한 비동기 작업을 수행합니다. 작업자 스레드에서 작업을 실행해야 하는 경우 [XAsyncSchedule](/reference/system/xasyncprovider/functions/xasyncschedule)을 호출합니다. OS 비동기 기본 형식을 사용하여 작업을 수행하고 이러한 기본 형식을 시간이 중요한 상황에서 안전할 만큼 충분히 빠르게 설정할 수 있다면, 그 방법이 좋습니다.

3. 작업자 스레드 콜백에서 다른 비동기 작업을 호출해야 하는 경우, 작업자에서 E\_PENDING을 반환할 수 있습니다. 작업자 내부에서 [XAsyncSchedule](/reference/system/xasyncprovider/functions/xasyncschedule)을 호출하여 추가 작업을 다시 예약할 수도 있습니다.

4. 모든 작업이 완료되면 [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)를 호출합니다.

5. [XAsyncGetResult](/reference/system/xasyncprovider/functions/xasyncgetresult) 주위에 강력한 형식의 래퍼를 제공하여 결과를 반환합니다.

6. 비동기 호출에 데이터 페이로드가 없는 경우, [XAsyncGetStatus](/reference/system/xasync/functions/xasyncgetstatus) 주위에 강력한 형식의 래퍼를 제공하고 [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)에 필요한 버퍼 크기로 0을 전달해야 합니다.

비동기 공급자 콜백은 다음 작업으로 호출됩니다.

* **Begin** 비동기 공급자는 [XAsyncBegin](/reference/system/xasyncprovider/functions/xasyncbegin) 중에 이 opcode로 호출됩니다. 공급자가 이 opcode를 구현하는 경우, [XAsyncSchedule](/reference/system/xasyncprovider/functions/xasyncschedule)을 호출하거나 외부 수단을 통해 비동기 작업을 시작해야 합니다. 이 콜백은 [XAsyncBegin](/reference/system/xasyncprovider/functions/xasyncbegin) 호출 체인에서 동기적으로 호출되므로 절대 차단해서는 안 됩니다.

* **DoWork** 작업 큐를 사용하여 비동기 작업을 예약하기 위해 [XAsyncSchedule](/reference/system/xasyncprovider/functions/xasyncschedule)이 호출된 경우에 호출됩니다. 공급자 함수는 필요한 모든 작업을 수행합니다. 완료되면 결과 코드와 데이터 페이로드 크기(호출에서 데이터 페이로드가 없는 경우 0일 수 있음)와 함께 [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)를 호출합니다. 더 많은 비동기 작업을 수행해야 하는 경우, 공급자는 해당 작업을 예약하고 E\_PENDING을 반환해야 합니다.

* **GetResult** 호출의 결과를 가져오기 위해 호출됩니다. 호출 완료 시 데이터 크기가 [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)에 전달되므로 여기서는 인수 확인이 필요하지 않습니다. 모든 버퍼와 버퍼 크기는 라이브러리에서 확인되었습니다.

* **Cancel** 사용자가 비동기 호출을 취소할 때 호출됩니다. 호출을 취소할 수 있는 경우 취소하고 결과 코드로 E\_ABORT를 사용하여 [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)를 호출합니다.

* **Cleanup** 호출이 완전히 완료되고 공급자가 동적 메모리를 삭제할 수 있을 때 호출됩니다.

비동기 공급자는 필요한 작업만 구현하면 됩니다. 예를 들어, 정리가 필요 없는 취소 불가능한 비동기 IO는 **GetResult**만 구현하면 됩니다.

다음은 팩토리얼을 비동기적으로 구현하는 **FactorialAsync** 메서드의 예입니다.

```c++ theme={null}
UINT64 Factorial(UINT64 value)
{
    UINT64 result = 1;

    while (value != 0)
    {       
        result *= value;
        value--;
    }

    return result;
}

HRESULT FactorialAsync(UINT64 value, XAsyncBlock* async)
{
    struct CallData
    {
        UINT64 value;
        UINT64 result;
    };

    CallData* data = new CallData();
    data->value = value;
    data->result = 1;

    HRESULT hr = XAsyncBegin (async, data, FactorialAsync, __FUNCTION__, []
        (XAsyncOp op, XAsyncProviderData* data)
    {
        CallData* d = (CallData*)data->context;

        switch (op)
        {
        case XAsyncOp::Begin:
            return XAsyncSchedule(data->async, 0);

        case XAsyncOp::Cleanup:
            delete d;
            break;

        case XAsyncOp::GetResult:
            CopyMemory(data->buffer, &d->result, sizeof(UINT64));
            break;
 
        case XAsyncOp::DoWork:
            data->result = Factorial(data.Value);
            XAsyncComplete(data->async, S_OK, sizeof(UINT64));
            break;
        }

        return S_OK;
    });

    return hr;
}

HRESULT FactorialAsyncResult(XAsyncBlock* async, UINT64* result)
{
    return XAsyncGetResult(async, FactorialAsync, sizeof(UINT64), result);
}
```

## 참고 API 문서

* [XAsync(API 콘텐츠)](/reference/system/xasync/xasync_members)
  * 함수
    * [XAsyncGetStatus](/reference/system/xasync/functions/xasyncgetstatus)
    * [XAsyncGetResultSize](/reference/system/xasync/functions/xasyncgetresultsize)
    * [XAsyncCancel](/reference/system/xasync/functions/xasynccancel)
    * [XAsyncCompletionRoutine](/reference/system/xasync/functions/xasynccompletionroutine)
    * [XAsyncRun](/reference/system/xasync/functions/xasyncrun)
  * 구조체
    * [XAsyncBlock](/reference/system/xasync/structs/xasyncblock)
* [XAsyncProvider(API 콘텐츠)](/reference/system/xasyncprovider/xasyncprovider_members)
  * 함수
    * [XAsyncGetResult](/reference/system/xasyncprovider/functions/xasyncgetresult)
    * [XAsyncBegin](/reference/system/xasyncprovider/functions/xasyncbegin)
    * [XAsyncSchedule](/reference/system/xasyncprovider/functions/xasyncschedule)
    * [XAsyncComplete](/reference/system/xasyncprovider/functions/xasynccomplete)
* [XTaskQueue(API 콘텐츠)](/reference/system/xtaskqueue/xtaskqueue_members)
  * 함수
    * [xtaskqueuecreate](/reference/system/xtaskqueue/functions/xtaskqueuecreate)
    * [xtaskqueuedispatch](/reference/system/xtaskqueue/functions/xtaskqueuedispatch)
* [xgamesave(API 콘텐츠)](/reference/system/xgamesave/xgamesave_members)
  * 함수
    * [XGameSaveGetRemainingQuotaAsync](/reference/system/xgamesave/functions/xgamesavegetremainingquotaasync)
    * [XGameSaveGetRemainingQuotaResult](/reference/system/xgamesave/functions/xgamesavegetremainingquotaresult)

## 참고 항목

[비동기 프로그래밍 설계 목표 및 개선 사항](/build/core-features/common/async/async-whitepaper)
[비동기 작업 큐 설계](/build/core-features/common/async/async-task-queue-design)


## Related topics

- [공통 GDK 기능](/ko/build/core-features/common/common-features-overview.md)
- [XBOX GDK의 비동기 프로그래밍 모델](/ko/build/core-features/common/async/index.md)
- [비동기 프로그래밍 개요](/ko/build/core-features/common/async/async-toc.md)
- [XTaskQueue](/ko/reference/system/xtaskqueue/xtaskqueue_members.md)
- [XAsync](/ko/reference/system/xasync/xasync_members.md)
