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.- Preferir llamadas sincrónicas en lugar de llamadas asincrónicas
- Proporcionar operaciones asincrónicas con sondeo
- Proporcionar operaciones asincrónicas con devoluciones de llamada
- Proporcionar control sobre el subproceso en el que se ejecuta el trabajo asincrónico
- 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:
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.
Uso de la API asincrónica
En primer lugar, veamos una API sincrónica en el siguiente ejemplo de código.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.
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.- Asincrónica con sondeo
- Asincrónica con devoluciones de llamada
- Control sobre el subproceso en el que se producen las devoluciones de llamada
- 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.
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.- 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.
- 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.
- 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.
- Cuando todo el trabajo esté completo, llame a XAsyncComplete.
- Proporcione un contenedor fuertemente tipado alrededor de XAsyncGetResult para devolver los resultados.
- 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.
- 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.
Documentación de referencia de la API
- XAsync (contenido de la API)
- Funciones
- Estructuras
- XAsyncProvider (contenido de la API)
- XTaskQueue (contenido de la API)
- Funciones
- xgamesave (contenido de la API)
