Skip to main content
Microsoft Game Development Kit (GDK) implementa un nuevo patrón para las API asincrónicas que aborda los comentarios que hemos recibido de los desarrolladores de juegos con respecto al patrón asincrónico implementado como parte del modelo de programación de XBOX One ERA. Nuestro objetivo es que este nuevo patrón sea mucho más fácil de integrar en las arquitecturas de juego típicas y proporcione a los desarrolladores de juegos el alto grado de control que han solicitado. En este tema se describe ese patrón de diseño y se ofrece una propuesta de biblioteca que puede usarse para implementar patrones asincrónicos.

Modelo conceptual

La programación asincrónica en Microsoft Game Development Kit (GDK) se divide en 2 componentes principales: tareas y colas de tareas. Aunque hay más funcionalidad en las bibliotecas, todo el modelo conceptual utiliza estos 2 componentes principales. Una tarea es un único conjunto de trabajo asincrónico que se puede iniciar, cuyo estado se puede comprobar, que potencialmente se puede cancelar, que se completa y que devuelve su información de finalización. Para el modelo de Microsoft Game Development Kit (GDK), las tareas se componen de dos cuerpos: la devolución de llamada de trabajo y la devolución de llamada de finalización. Esto permite más control, como el procesamiento paralelo completo o el trabajo en paralelo combinado con una finalización de un solo subproceso. Una cola de tareas es un contenedor que pone en cola tanto las devoluciones de llamada de trabajo como las de finalización para su ejecución posterior. Hay dos colas internas en una cola de tareas, denominadas puertos, que controlan las devoluciones de llamada de trabajo y de finalización por separado. Se denominan puerto de trabajo y puerto de finalización. Figura 1. Diagrama de una tarea y una cola de tareas Cada puerto de la cola de tareas se configura de forma diferente en el momento de la creación para crear un comportamiento de ejecución de devoluciones de llamada distinto. Por ejemplo, el puerto de trabajo podría configurarse para ser asincrónico y el puerto de finalización podría configurarse para ejecutarse en serie en un subproceso principal. Se puede establecer una configuración manual para habilitar el control completo sobre el comportamiento de ejecución. Los modos de configuración de puertos se explican más adelante. Cuando se inicia una tarea asincrónica, las devoluciones de llamada no se ponen en cola en la cola de tareas inmediatamente. Un proveedor asincrónico controla los cambios de estado para garantizar que el trabajo se ponga en cola y se distribuya antes de que la devolución de llamada de finalización se ponga en cola y se distribuya. La cola de tareas no controla directamente los subprocesos. En su lugar, depende de una llamada externa para distribuir sus puertos. Las llamadas externas determinan el comportamiento de los subprocesos y la simultaneidad. La propia cola de tareas es completamente segura para subprocesos. Figura 2. Puerto distribuido en varios subprocesos ¡Eso es esencialmente todo! Las devoluciones de llamada de una tarea se ponen en cola en los puertos de trabajo y de finalización de una cola de tareas, y esa cola de tareas hace que esas devoluciones de llamada se distribuyan de alguna manera. La API contiene un conjunto completo de funcionalidad para administrar colas de tareas, comprobar el estado de las devoluciones de llamada, hacer seguimiento de los datos de trabajo, crear un control de tareas personalizado y mucho más. Las llamadas API asincrónicas de Microsoft Game Development Kit (GDK) siempre implementan la devolución de llamada de trabajo internamente y las devoluciones de llamada de finalización son siempre opcionales. Para usos más allá de las llamadas asincrónicas de Microsoft Game Development Kit (GDK), debe proporcionar la devolución de llamada de trabajo.

Requisitos

Los desarrolladores de juegos han enumerado los siguientes requisitos para las llamadas API.
  1. Preferir llamadas sincrónicas en lugar de llamadas asincrónicas
  2. Proporcionar operaciones asincrónicas con sondeo
  3. Proporcionar operaciones asincrónicas con devoluciones de llamada
  4. Proporcionar control sobre el subproceso en el que se ejecuta el trabajo asincrónico
  5. Proporcionar control sobre el subproceso en el que se ejecutan las devoluciones de llamada de finalización

Tipos de API

Microsoft Game Development Kit (GDK) se esfuerza por ser muy directo en el diseño de su API. Los desarrolladores de juegos son expertos en ajustar su código para maximizar el uso del hardware. Les damos el control siempre que es posible. Las implementaciones de API se dividen en los siguientes tipos.
  • Seguras para subprocesos sensibles al tiempo: una API segura para subprocesos sensibles al tiempo es aquella que se puede llamar en un subproceso sensible al tiempo. Tenga en cuenta que, aunque esto normalmente significa que la API es trivial o muy rápida, el concepto clave es que las características de rendimiento de la API son coherentes. Siempre son sincrónicas y nunca necesitan tener una versión asincrónica. Estas API deben documentarse como seguras para tiempo sensible.
  • No seguras para subprocesos sensibles al tiempo: no es seguro llamar a estas API desde el subproceso de representación. Sus características de rendimiento pueden variar considerablemente. La mayoría de las API pertenecen a esta categoría.
  • Asincrónicas: estas API son de naturaleza asincrónica, como una llamada a un servicio web. Usan el patrón asincrónico descrito en este tema. Las API asincrónicas no son tan comunes en Microsoft Game Development Kit (GDK) como en el modelo de programación de XBOX One ERA: una API asincrónica suele ser de larga duración y cancelable. Excepto en algunos casos de uso específicos, las API asincrónicas tendrán una versión sincrónica no segura para tiempo crítico. La llamada a una API asincrónica siempre debe ser segura para tiempo crítico.
  • Notificaciones: las notificaciones son de naturaleza periódica y no tienen un final definido. Están relacionadas con las API asincrónicas pero, debido a su naturaleza periódica, deben tener un aspecto y un comportamiento diferentes para los desarrolladores. Registrarse para una notificación siempre debe ser seguro para tiempo crítico.

Patrón de API asincrónica

Microsoft Game Development Kit (GDK) presenta un patrón de API asincrónica de uso general que los componentes de Microsoft Game Development Kit (GDK) pueden usar para proporcionar compatibilidad asincrónica coherente. En el núcleo hay una estructura similar a OVERLAPPED denominada XAsyncBlock:
Un XAsyncBlock es una estructura proporcionada por el autor de la llamada. El autor de la llamada rellena los campos opcionales de esta estructura, como se muestra en la tabla siguiente. El sistema usa los campos Internal y no deben modificarse. Los campos que puede establecer el usuario en esta estructura no deben modificarse durante una operación asincrónica. Un XAsyncBlock debe permanecer en memoria durante toda la duración de la operación asincrónica. Si el XAsyncBlock se asigna dinámicamente, la devolución de llamada de finalización es el momento más temprano en el que se puede eliminar. Además de XAsyncBlock, hay un pequeño número de API auxiliares, que se muestran a continuación.
XAsyncGetStatus devuelve el estado de una llamada asincrónica. Cuando comienza la llamada, este estado es E_PENDING. Cambia a S_OK o a un error específico cuando se completa. Si se cancela la llamada, devuelve E_ABORT. XAsyncGetResultSize devuelve el tamaño de búfer necesario para obtener los resultados de la llamada. La API real para capturar los resultados se adapta a cada llamada asincrónica. XAsyncCancel puede usarse para cancelar una llamada. La cancelación depende de la operación que se cancela y puede producirse de forma sincrónica, asincrónica o no producirse en absoluto. Si se cancela una operación, XAsyncGetResult, XAsyncGetResultSize o XAsyncGetStatus devuelven E_ABORT. Una llamada cancelada señala el parámetro XAsyncCompletionRoutine del XAsyncBlock e invoca su devolución de llamada. XAsyncRun es un método auxiliar que puede ejecutar de forma asincrónica cualquier código.

Uso de la API asincrónica

En primer lugar, veamos una API sincrónica en el siguiente ejemplo de código.
Esta API llama a un servicio web para determinar cuánto almacenamiento de partidas guardadas queda todavía. Para agregar compatibilidad asincrónica, declaramos un par de nuevas API.
XGameSaveGetRemainingQuotaAsync devuelve S_OK si la llamada asincrónica se ha iniciado (como esta API es solo asincrónica, no tiene sentido devolver E_PENDING). XGameSaveGetRemainingQuotaResult devuelve E_PENDING hasta que la llamada se completa. Veamos esto en la práctica de la siguiente manera.
Todos los XAsyncBlocks requieren una cola de tareas (descrita a continuación), que controla dónde y cómo se ejecuta la llamada asincrónica. Se usa una cola de tareas de todo el proceso si no se proporciona ninguna. Tenga en cuenta que el XAsyncBlock debe permanecer en memoria durante toda la duración de la llamada asincrónica. En este ejemplo, se asignó dinámicamente y se eliminó en la devolución de llamada de finalización. También podría almacenarse como una variable global o de miembro. Se produce un comportamiento indefinido si el mismo XAsyncBlock se usa para más de una llamada asincrónica a la vez. XGameSaveGetRemainingQuotaResult completa el ciclo de una llamada asincrónica. Libera los datos internos del bloque asincrónico, por lo que el bloque ahora puede usarse para una nueva llamada. Las llamadas posteriores a XGameSaveGetRemainingQuotaResult generan un error. XGameSaveGetRemainingQuotaAsync y XGameSaveGetRemainingQuotaResult también están emparejados dentro del bloque asincrónico: se produce un error si combina incorrectamente una llamada asincrónica con otra API de resultados. Si una llamada asincrónica no tiene carga de datos, es decir, solo importa el estado HRESULT, defina un método Result que tome solo el bloque asincrónico, como se muestra a continuación.

Control de la distribución del trabajo

¿Qué subproceso realizó el trabajo asincrónico en las llamadas anteriores? ¿Qué subproceso invocó la devolución de llamada de finalización? Eso lo decide la cola de tareas asignada al XAsyncBlock. Las colas de tareas tienen dos “puertos”: un puerto de trabajo y un puerto de finalización. Cada puerto tiene un modo de distribución que determina cómo se procesan las devoluciones de llamada en cola en un puerto. Hay varios modos de distribución.
  • Grupo de subprocesos: las devoluciones de llamada puestas en cola en una cola de grupo de subprocesos se ejecutan en el grupo de subprocesos del sistema. El grupo de subprocesos invoca las llamadas en paralelo, tomando por turnos una llamada de la cola para ejecutarla a medida que los subprocesos del grupo quedan disponibles.
  • Grupo de subprocesos serializado: las devoluciones de llamada se ponen en cola y se ejecutan en el grupo de subprocesos, pero de una en una.
  • Manual: las devoluciones de llamada puestas en cola en una cola manual no se distribuyen automáticamente. Corresponde al desarrollador distribuirlas en el subproceso que desee.
  • Inmediato: el modo de distribución inmediato no pone nada en cola. Ejecuta inmediatamente la llamada en el subproceso que envió la devolución de llamada.
Hay una cola de tareas de proceso predeterminada configurada, de modo que tanto los puertos de trabajo como los puertos de finalización se distribuyen a través del grupo de subprocesos del sistema. Esta cola de tareas de proceso se usa si no se pasa ningún parámetro de cola en el XAsyncBlock. Un juego también puede deshabilitar la cola de tareas del proceso, lo que requiere que se pase una cola en el XAsyncBlock. Nuestra expectativa es que muchos desarrolladores elijan el modo de distribución manual para ejercer un control completo sobre cuándo y dónde se ejecutan el trabajo asincrónico y las devoluciones de llamada de finalización. Para obtener más información sobre las colas de tareas, consulte Diseño de colas de tareas asincrónicas.

Notificaciones

Una notificación podría no tener final y podría llamarse muchas veces. Las notificaciones deben admitir un subconjunto de los requisitos de una llamada asincrónica.
  1. Asincrónica con sondeo
  2. Asincrónica con devoluciones de llamada
  3. Control sobre el subproceso en el que se producen las devoluciones de llamada
Las notificaciones usan una cola de tareas para permitir que el desarrollador controle el subproceso de devolución de llamada pero, por lo demás, no usan bloques asincrónicos: están diseñadas para parecerse más a eventos estándar con métodos Register y Unregister.
  • Un método Register que toma los parámetros específicos de la llamada, una cola de tareas, un contexto void opcional y un puntero de devolución de llamada fuertemente tipado. El último parámetro es un parámetro out que devuelve un token.
  • Un método Unregister que toma el contexto específico de la llamada y el token.
  • El sondeo se admite agregando un método independiente no relacionado con la devolución de llamada de la notificación.
Veamos el ejemplo siguiente, que podría capturar mensajes de Windows.
Tenga en cuenta que, en este ejemplo, UnregisterMessageAvailable toma un parámetro final “wait” y devuelve un bool. Esto permite a los autores de llamadas decidir cómo controlar la anulación del registro mientras se está invocando una llamada.

Biblioteca asincrónica

Para facilitar la creación de API coherentes que admitan el patrón asincrónico, proporcionamos una biblioteca que puede usarse para implementar la “fontanería asincrónica” de una API. La API de la biblioteca tiene el siguiente aspecto.
Esta API usa una única devolución de llamada, combinada con un valor de operación que indica por qué se llama a la API. También hay una única estructura de datos que se rellena a medida que avanza la llamada. Para usar esta API, haga lo siguiente.
  1. Llame a XAsyncBegin con el bloque asincrónico pasado por el autor de la llamada y proporcione una devolución de llamada que proporcione la implementación.
  2. Realice el trabajo asincrónico de la llamada. Si necesita ejecutar el trabajo en un subproceso de trabajo, llame a XAsyncSchedule. Si puede realizar el trabajo usando primitivas asincrónicas del sistema operativo y configurar esas primitivas con la rapidez suficiente para seguir siendo seguro para tiempo crítico, esa opción es preferible.
  3. Si necesita invocar otro trabajo asincrónico desde una devolución de llamada de subproceso de trabajo, puede devolver E_PENDING desde el trabajo. También puede llamar a XAsyncSchedule desde dentro de un trabajo para volver a programar trabajo adicional.
  4. Cuando todo el trabajo esté completo, llame a XAsyncComplete.
  5. Proporcione un contenedor fuertemente tipado alrededor de XAsyncGetResult para devolver los resultados.
  6. Si su llamada asincrónica no tiene carga de datos, debe proporcionar un contenedor fuertemente tipado alrededor de XAsyncGetStatus y pasar cero como tamaño de búfer requerido a XAsyncComplete.
La devolución de llamada del proveedor asincrónico se invoca con las siguientes operaciones.
  • Begin: se invoca un proveedor asincrónico con este código de operación durante XAsyncBegin. Si el proveedor implementa este código de operación, debe iniciar su tarea asincrónica llamando a XAsyncSchedule o mediante medios externos. Se llama a esta devolución de llamada de forma sincrónica en la cadena de llamadas de XAsyncBegin, por lo que nunca debe bloquearse.
  • DoWork: se llama en los casos en que se llamó a XAsyncSchedule para programar el trabajo asincrónico mediante la cola de tareas. La función del proveedor realiza el trabajo que necesite. Cuando se completa, llama a XAsyncComplete con el código de resultado y el tamaño de la carga de datos, que puede ser cero si la llamada no tiene carga de datos. Si es necesario realizar más trabajo asincrónico, el proveedor puede programar ese trabajo y debe devolver E_PENDING.
  • GetResult: se llama para capturar el resultado de la llamada. Dado que el tamaño de los datos se pasa a XAsyncComplete durante la finalización de la llamada, aquí no se necesita comprobar los argumentos: la biblioteca ha verificado todos los búferes y los tamaños de búfer.
  • Cancel: se llama cuando el usuario cancela una llamada asincrónica. Si la llamada puede cancelarse, cancélela y llame a XAsyncComplete con E_ABORT como código de resultado.
  • Cleanup: se llama cuando la llamada ha finalizado completamente, y el proveedor puede eliminar cualquier memoria dinámica.
Un proveedor asincrónico solo necesita implementar las operaciones que necesita. Por ejemplo, una E/S asincrónica no cancelable que no requiere limpieza solo necesita implementar GetResult. A continuación se muestra un ejemplo de un método FactorialAsync que implementa el factorial de forma asincrónica.

Documentación de referencia de la API

Consulte también

Objetivos de diseño y mejoras de la programación asincrónica Diseño de colas de tareas asincrónicas
Última modificación el 28 de agosto de 2026