개념 모델
Microsoft Game Development Kit(GDK)의 비동기 프로그래밍은 크게 두 가지 구성 요소로 나뉩니다. 작업(Task)과 작업 큐(Task Queue)입니다. 라이브러리에는 더 많은 기능이 있지만, 전체 개념 모델은 이 두 가지 주요 구성 요소를 활용합니다. 작업은 시작, 상태 확인, 취소 가능, 완료, 완료 정보 반환이 가능한 단일 비동기 작업 세트입니다. Microsoft Game Development Kit(GDK) 모델에서 작업은 두 개의 본문으로 구성됩니다. 즉, 작업 콜백과 완료 콜백입니다. 이를 통해 완전 병렬 처리나 병렬 작업과 단일 스레드 완료를 결합한 방식과 같은 더 많은 제어가 가능합니다. 작업 큐는 나중에 실행하기 위해 작업 콜백과 완료 콜백을 모두 큐에 넣는 컨테이너입니다. 작업 큐에는 포트라고 하는 두 개의 내부 큐가 있으며 작업 콜백과 완료 콜백을 각각 별도로 처리합니다. 이를 각각 작업 포트와 완료 포트라고 합니다. 그림 1. 작업과 작업 큐 다이어그램 작업 큐의 각 포트는 만들 때 서로 다른 콜백 실행 동작을 만들도록 다르게 구성됩니다. 예를 들어 작업 포트는 비동기로 구성하고, 완료 포트는 주 스레드에서 직렬로 실행되도록 구성할 수 있습니다. 실행 동작을 완전히 제어할 수 있도록 수동 설정을 지정할 수도 있습니다. 포트 구성 모드는 아래에서 설명합니다. 비동기 작업을 시작할 때 콜백이 즉시 작업 큐에 큐잉되지 않습니다. 비동기 공급자가 상태 변경을 처리하여 완료 콜백이 큐에 들어가고 디스패치되기 전에 작업이 큐에 들어가고 디스패치되도록 보장합니다. 작업 큐 자체는 스레딩을 직접 처리하지 않습니다. 대신 포트를 디스패치하기 위한 외부 호출에 의존합니다. 외부 호출은 스레딩 및 동시성 동작을 결정합니다. 작업 큐 자체는 완전히 스레드로부터 안전합니다. 그림 2. 여러 스레드로 디스패치되는 포트 기본적으로 이것이 전부입니다! 작업의 콜백은 작업 큐의 작업 포트와 완료 포트에 큐잉되며, 해당 작업 큐는 이러한 콜백을 어떤 방식으로 디스패치합니다. API에는 작업 큐 관리, 콜백 상태 확인, 작업 데이터 추적, 사용자 지정 작업 처리 만들기 등의 전체 기능 모음이 포함되어 있습니다. Microsoft Game Development Kit(GDK) 비동기 API 호출은 항상 내부적으로 작업 콜백을 구현하며, 완료 콜백은 항상 선택 사항입니다. Microsoft Game Development Kit(GDK) 비동기 호출 이외의 용도로 사용하는 경우, 사용자가 작업 콜백을 제공해야 합니다.요구 사항
게임 개발자는 API 호출에 대한 다음과 같은 요구 사항을 나열했습니다.- 비동기 호출보다 동기 호출을 선호합니다.
- 폴링이 있는 비동기를 제공합니다.
- 콜백이 있는 비동기를 제공합니다.
- 비동기 작업이 실행되는 스레드에 대한 제어를 제공합니다.
- 완료 콜백이 실행되는 스레드에 대한 제어를 제공합니다.
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이 있습니다.
Internal 필드는 시스템이 사용하며 수정해서는 안 됩니다. 이 구조체의 사용자가 설정할 수 있는 필드는 비동기 작업 중에 수정해서는 안 됩니다. XAsyncBlock은 비동기 작업의 수명 동안 메모리에 유지되어야 합니다. XAsyncBlock이 동적으로 할당된 경우, 완료 콜백이 이를 삭제할 수 있는 가장 이른 시점입니다.
XAsyncBlock 외에도, 다음과 같이 소수의 도우미 API가 있습니다.
비동기 API 사용
먼저 다음 코드 예제에서 동기 API를 살펴보겠습니다.작업 디스패치 제어
이전 호출에서 비동기 작업은 어떤 스레드에서 수행되었을까요? 어떤 스레드가 완료 콜백을 호출했을까요? 이는 XAsyncBlock에 할당된 작업 큐에 의해 결정됩니다. 작업 큐에는 두 개의 “포트”가 있습니다. 작업 포트와 완료 포트입니다. 각 포트에는 포트에 큐잉된 콜백이 어떻게 처리되는지 결정하는 디스패치 모드가 있습니다. 여러 가지 디스패치 모드가 있습니다.- Thread pool(스레드 풀): 스레드 풀 큐에 큐잉된 콜백은 시스템 스레드 풀에서 실행됩니다. 스레드 풀은 스레드 풀 스레드가 사용 가능해질 때마다 큐에서 호출을 하나씩 가져와 병렬로 호출합니다.
- Serialized thread pool(직렬화된 스레드 풀): 콜백이 큐잉되고 스레드 풀에서 실행되지만 한 번에 하나씩 실행됩니다.
- Manual(수동): 수동 큐에 큐잉된 콜백은 자동으로 디스패치되지 않습니다. 개발자가 원하는 스레드에서 디스패치해야 합니다.
- Immediate(즉시): 즉시 디스패치 모드는 큐잉을 전혀 하지 않습니다. 콜백을 제출한 스레드에서 즉시 호출을 실행합니다.
알림
알림에는 종료 시점이 없을 수 있으며 여러 번 호출될 수 있습니다. 알림은 비동기 호출 요구 사항의 하위 집합을 지원해야 합니다.- 폴링이 있는 비동기
- 콜백이 있는 비동기
- 콜백이 발생하는 스레드에 대한 제어
- 호출별 매개 변수, 작업 큐, 선택적 void 컨텍스트, 강력한 형식의 콜백 포인터를 받는 Register 메서드입니다. 마지막 매개 변수는 토큰을 반환하는 out 매개 변수입니다.
- 호출별 컨텍스트와 토큰을 받는 Unregister 메서드입니다.
- 폴링은 알림 콜백과 관련이 없는 별도의 메서드를 추가하여 지원됩니다.
비동기 라이브러리
비동기 패턴을 지원하는 일관된 API를 더 쉽게 만들 수 있도록, API의 “async plumbing”을 구현하는 데 사용할 수 있는 라이브러리를 제공합니다. 라이브러리의 API는 다음과 같습니다.- 호출자가 전달한 async 블록으로 XAsyncBegin을 호출하고, 구현을 제공하는 콜백을 제공합니다.
- 호출에 대한 비동기 작업을 수행합니다. 작업자 스레드에서 작업을 실행해야 하는 경우 XAsyncSchedule을 호출합니다. OS 비동기 기본 형식을 사용하여 작업을 수행하고 이러한 기본 형식을 시간이 중요한 상황에서 안전할 만큼 충분히 빠르게 설정할 수 있다면, 그 방법이 좋습니다.
- 작업자 스레드 콜백에서 다른 비동기 작업을 호출해야 하는 경우, 작업자에서 E_PENDING을 반환할 수 있습니다. 작업자 내부에서 XAsyncSchedule을 호출하여 추가 작업을 다시 예약할 수도 있습니다.
- 모든 작업이 완료되면 XAsyncComplete를 호출합니다.
- XAsyncGetResult 주위에 강력한 형식의 래퍼를 제공하여 결과를 반환합니다.
- 비동기 호출에 데이터 페이로드가 없는 경우, XAsyncGetStatus 주위에 강력한 형식의 래퍼를 제공하고 XAsyncComplete에 필요한 버퍼 크기로 0을 전달해야 합니다.
- Begin 비동기 공급자는 XAsyncBegin 중에 이 opcode로 호출됩니다. 공급자가 이 opcode를 구현하는 경우, XAsyncSchedule을 호출하거나 외부 수단을 통해 비동기 작업을 시작해야 합니다. 이 콜백은 XAsyncBegin 호출 체인에서 동기적으로 호출되므로 절대 차단해서는 안 됩니다.
- DoWork 작업 큐를 사용하여 비동기 작업을 예약하기 위해 XAsyncSchedule이 호출된 경우에 호출됩니다. 공급자 함수는 필요한 모든 작업을 수행합니다. 완료되면 결과 코드와 데이터 페이로드 크기(호출에서 데이터 페이로드가 없는 경우 0일 수 있음)와 함께 XAsyncComplete를 호출합니다. 더 많은 비동기 작업을 수행해야 하는 경우, 공급자는 해당 작업을 예약하고 E_PENDING을 반환해야 합니다.
- GetResult 호출의 결과를 가져오기 위해 호출됩니다. 호출 완료 시 데이터 크기가 XAsyncComplete에 전달되므로 여기서는 인수 확인이 필요하지 않습니다. 모든 버퍼와 버퍼 크기는 라이브러리에서 확인되었습니다.
- Cancel 사용자가 비동기 호출을 취소할 때 호출됩니다. 호출을 취소할 수 있는 경우 취소하고 결과 코드로 E_ABORT를 사용하여 XAsyncComplete를 호출합니다.
- Cleanup 호출이 완전히 완료되고 공급자가 동적 메모리를 삭제할 수 있을 때 호출됩니다.
