Modèle conceptuel
La programmation asynchrone dans le Microsoft Game Development Kit (GDK) se divise en 2 composants principaux : les tâches et les files d’attente de tâches. Bien que les bibliothèques offrent davantage de fonctionnalités, l’ensemble du modèle conceptuel repose sur ces 2 composants principaux. Une tâche est un ensemble unique de travail asynchrone qui peut être démarré, dont l’état peut être vérifié, qui peut éventuellement être annulé, qui se termine et qui retourne ses informations d’achèvement. Dans le modèle du Microsoft Game Development Kit (GDK), les tâches sont composées de deux corps : le rappel de travail et le rappel d’achèvement. Cela permet un plus grand contrôle, par exemple un traitement entièrement parallèle ou un travail parallèle combiné à un achèvement sur un seul thread. Une file d’attente de tâches est un conteneur qui met en file d’attente les rappels de travail et d’achèvement en vue d’une exécution ultérieure. Une file d’attente de tâches comporte deux files internes, appelées ports, qui gèrent séparément les rappels de travail et d’achèvement. Il s’agit du port de travail et du port d’achèvement. Figure 1. Diagramme d’une tâche et d’une file d’attente de tâches

Exigences
Les développeurs de jeux vidéo ont énuméré les exigences suivantes pour les appels d’API.- Privilégier les appels synchrones plutôt que les appels asynchrones
- Offrir l’asynchronisme avec interrogation
- Offrir l’asynchronisme avec rappels
- Offrir le contrôle du thread sur lequel le travail asynchrone s’exécute
- Offrir le contrôle du thread sur lequel les rappels d’achèvement s’exécutent
Types d’API
Le Microsoft Game Development Kit (GDK) s’efforce d’être très simple dans la conception de ses API. Les développeurs de jeux vidéo sont des experts dans l’optimisation de leur code pour tirer le meilleur parti du matériel. Nous leur donnons le contrôle chaque fois que c’est possible. Les implémentations d’API se répartissent dans les types suivants.- Sécuritaire en contexte critique : Une API sécuritaire en contexte critique est une API qui peut être appelée sur un thread critique en matière de temps. Notez que, même si cela signifie généralement que l’API est triviale ou très rapide, le concept clé est que les caractéristiques de performance de l’API sont constantes. Ces API sont toujours synchrones et n’ont jamais besoin d’une version asynchrone. Ces API doivent être documentées comme étant sécuritaires en contexte critique.
- Non sécuritaire en contexte critique : Ces API ne peuvent pas être appelées en toute sécurité à partir du thread de rendu. Leurs caractéristiques de performance peuvent varier considérablement. La plupart des API entrent dans cette catégorie.
- Asynchrone : Ces API sont asynchrones par nature, comme un appel de service Web. Elles utilisent le modèle asynchrone décrit dans cette rubrique. Les API asynchrones sont moins courantes dans le Microsoft Game Development Kit (GDK) que dans le modèle de programmation ERA de la XBOX One : une API asynchrone est généralement de longue durée et annulable. Sauf dans quelques cas d’utilisation précis, les API asynchrones disposent d’une version synchrone non sécuritaire en contexte critique. L’appel d’une API asynchrone doit toujours être sécuritaire en contexte critique.
- Notifications : Les notifications sont de nature périodique et n’ont pas de fin définie. Elles sont liées aux API asynchrones, mais en raison de leur nature périodique, elles doivent avoir une apparence et un comportement différents pour les développeurs. L’inscription à une notification doit toujours être sécuritaire en contexte critique.
Modèle d’API asynchrone
Le Microsoft Game Development Kit (GDK) introduit un modèle d’API asynchrone à usage général que les composants du Microsoft Game Development Kit (GDK) peuvent utiliser pour offrir une prise en charge asynchrone cohérente. Au cœur de ce modèle se trouve une structure semblable à OVERLAPPED, appelée XAsyncBlock :
Les champs Internal sont utilisés par le système et ne doivent pas être modifiés.
Les champs de cette structure définissables par l’utilisateur ne doivent pas être modifiés
pendant une opération asynchrone. Un XAsyncBlock doit rester en mémoire pendant toute la
durée de vie de l’opération asynchrone. Si le XAsyncBlock est alloué dynamiquement,
le rappel d’achèvement est le moment le plus tôt où il peut être supprimé.
En plus de XAsyncBlock, il existe un petit nombre d’API d’assistance, présentées ci-dessous.
Utilisation des API asynchrones
Examinons d’abord une API synchrone dans l’exemple de code suivant.Contrôle de la distribution du travail
Quel thread a effectué le travail asynchrone dans les appels précédents? Quel thread a appelé le rappel d’achèvement? C’est la file d’attente de tâches affectée au XAsyncBlock qui en décide. Les files d’attente de tâches ont deux « ports » : un port de travail et un port d’achèvement. Chaque port possède un mode de distribution qui détermine la façon dont les rappels mis en file d’attente sur un port sont traités. Il existe plusieurs modes de distribution.- Pool de threads : Les rappels mis dans une file d’attente de pool de threads sont exécutés sur le pool de threads du système. Le pool de threads appelle les rappels en parallèle, en prenant tour à tour un appel à exécuter dans la file d’attente à mesure que des threads du pool deviennent disponibles.
- Pool de threads sérialisé : Les rappels sont mis en file d’attente et exécutés sur le pool de threads, mais un à la fois.
- Manuel : Les rappels mis dans une file d’attente manuelle ne sont pas automatiquement distribués. Il revient au développeur de les distribuer sur le thread de son choix.
- Immédiat : Le mode de distribution immédiat n’effectue aucune mise en file d’attente. Il exécute immédiatement l’appel sur le thread qui a soumis le rappel.
Notifications
Une notification peut ne pas avoir de fin et peut être appelée de nombreuses fois. Les notifications doivent prendre en charge un sous-ensemble des exigences d’un appel asynchrone.- Asynchronisme avec interrogation
- Asynchronisme avec rappels
- Contrôle du thread sur lequel les rappels se produisent
- Une méthode Register qui prend tous les paramètres propres à l’appel, une file d’attente de tâches, un contexte void facultatif et un pointeur de rappel fortement typé. Le dernier paramètre est un paramètre out qui retourne un jeton.
- Une méthode Unregister qui prend tout contexte propre à l’appel et le jeton.
- L’interrogation est prise en charge par l’ajout d’une méthode distincte qui n’est pas liée au rappel de notification.
Bibliothèque asynchrone
Pour faciliter la création d’API cohérentes qui prennent en charge le modèle asynchrone, nous fournissons une bibliothèque qui peut être utilisée pour implémenter la « plomberie asynchrone » d’une API. L’API de la bibliothèque se présente comme suit.- Appelez XAsyncBegin avec le bloc asynchrone passé par l’appelant, et fournissez un rappel qui fournit l’implémentation.
- Effectuez le travail asynchrone de l’appel. Si vous devez exécuter le travail sur un thread de travail, appelez XAsyncSchedule. Si vous pouvez effectuer le travail en utilisant des primitives asynchrones du système d’exploitation et configurer ces primitives assez rapidement pour demeurer sécuritaire en contexte critique, cette approche est à privilégier.
- Si vous devez appeler un autre travail asynchrone à partir d’un rappel de thread de travail, vous pouvez retourner E_PENDING à partir du thread de travail. Vous pouvez également appeler XAsyncSchedule à partir d’un thread de travail pour replanifier du travail supplémentaire.
- Lorsque tout le travail est terminé, appelez XAsyncComplete.
- Fournissez un wrapper fortement typé autour de XAsyncGetResult pour retourner les résultats.
- Si votre appel asynchrone n’a aucune charge utile de données, vous devez fournir un wrapper fortement typé autour de XAsyncGetStatus et passer zéro comme taille de mémoire tampon requise à XAsyncComplete.
- Begin Un fournisseur asynchrone est appelé avec ce code d’opération pendant XAsyncBegin. Si le fournisseur implémente ce code d’opération, il doit démarrer sa tâche asynchrone en appelant XAsyncSchedule ou par des moyens externes. Ce rappel est appelé de façon synchrone dans la chaîne d’appels de XAsyncBegin; il ne doit donc jamais bloquer.
- DoWork Appelé dans les cas où XAsyncSchedule a été appelée pour planifier un travail asynchrone au moyen de la file d’attente de tâches. La fonction du fournisseur effectue tout le travail nécessaire. Une fois terminé, elle appelle XAsyncComplete avec le code de résultat et la taille de la charge utile de données, qui peut être zéro si l’appel n’a aucune charge utile de données. Si d’autres travaux asynchrones doivent être effectués, le fournisseur peut planifier ce travail et doit retourner E_PENDING.
- GetResult Appelé pour récupérer le résultat de l’appel. Comme la taille des données est passée à XAsyncComplete lors de l’achèvement de l’appel, aucune vérification des arguments n’est nécessaire ici : toutes les mémoires tampons et leurs tailles ont été vérifiées par la bibliothèque.
- Cancel Appelé lorsque l’utilisateur annule un appel asynchrone. Si l’appel peut être annulé, annulez-le et appelez XAsyncComplete avec E_ABORT comme code de résultat.
- Cleanup Appelé lorsque l’appel est complètement terminé et que le fournisseur peut supprimer toute mémoire dynamique.
Documentation de référence de l’API
- XAsync (contenu de l’API)
- Fonctions
- Structures
- XAsyncProvider (contenu de l’API)
- XTaskQueue (contenu de l’API)
- Fonctions
- xgamesave (contenu de l’API)
