Appels de procédure asynchrone

Un appel de procédure asynchrone (APC) est une fonction qui s’exécute de manière asynchrone dans le contexte d’un thread particulier. Lorsqu’un APC est mis en file d’attente vers un thread, le système émet une interruption logicielle. La prochaine fois que le thread est planifié, il exécutera la fonction APC. Un APC généré par le système est appelé APC en mode noyau. Un APC généré par une application est appelé APC en mode utilisateur. Un thread doit être dans un état alertable pour exécuter un APC en mode utilisateur.

Chaque thread a sa propre file d’attente APC. Une application met en file d’attente un APC vers un thread en appelant la fonction QueueUserAPC . Le thread appelant spécifie l’adresse d’une fonction APC dans l’appel à QueueUserAPC. La mise en file d’attente d’un APC est une demande pour que le thread appelle la fonction APC.

Lorsqu’un APC en mode utilisateur est mis en file d’attente, le thread sur lequel il est mis en file d’attente n’est pas dirigé vers l’appel de la fonction APC, sauf s’il est dans un état alertable. Un thread entre dans un état d’alerte lorsqu’il appelle la fonction SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx ou WaitForSingleObjectEx . Si l’attente est satisfaite avant que l’APC soit mis en file d’attente, le thread n’est plus dans un état d’attente alertable, de sorte que la fonction APC ne sera pas exécutée. Toutefois, l’APC étant toujours en file d’attente, la fonction APC sera exécutée lorsque le thread appelle une autre fonction d’attente alertable.

Les fonctions ReadFileEx, SetWaitableTimer, SetWaitableTimerEx et WriteFileEx sont implémentées à l’aide d’un APC comme mécanisme de rappel de notification d’achèvement.

Si vous utilisez un pool de threads, notez que les API ne fonctionnent pas aussi bien que d’autres mécanismes de signalisation, car le système contrôle la durée de vie des threads de pool de threads. Il est donc possible qu’un thread soit arrêté avant la remise de la notification. Au lieu d’utiliser un mécanisme de signalisation basé sur APC tel que le paramètre pfnCompletionRoutine de SetWaitableTimer ou SetWaitableTimerEx, utilisez un objet d’attente tel qu’un minuteur créé avec CreateThreadpoolTimer. Pour les E/S, utilisez un objet d’achèvement d’E/S créé avec CreateThreadpoolIo ou une structure CHEVAUCHEMENT BASÉE sur hEvent où l’événement peut être passé à la fonction SetThreadpoolWait.

Synchronisation interne

Lorsqu’une demande d’E/S est émise, une structure est allouée pour représenter la demande. Cette structure est appelée paquet de demande d’E/S (IRP). Avec les E/S synchrones, le thread génère l’IRP, l’envoie à la pile de l’appareil et attend dans le noyau que l’IRP se termine. Avec les E/S asynchrones, le thread génère l’IRP et l’envoie à la pile d’appareils. La pile peut terminer l’IRP immédiatement, ou elle peut renvoyer un status en attente indiquant que la demande est en cours. Dans ce cas, l’IRP est toujours associé au thread. Il est donc annulé si le thread se termine ou appelle une fonction telle que CancelIo. Entre-temps, le thread peut continuer à effectuer d’autres tâches pendant que la pile d’appareils continue de traiter l’IRP.

Il existe plusieurs façons pour le système d’indiquer que l’IRP est terminé :

  • Mettez à jour la structure qui se chevauche avec le résultat de l’opération afin que le thread puisse interroger pour déterminer si l’opération est terminée.
  • Signalez l’événement dans la structure qui se chevauche afin qu’un thread puisse se synchroniser sur l’événement et être réveillé à la fin de l’opération.
  • Mettez en file d’attente l’IRP vers l’APC en attente du thread afin que le thread exécute la routine APC lorsqu’il entre dans un état d’attente pouvant être alerté et retourne de l’opération d’attente avec un status indiquant qu’il a exécuté une ou plusieurs routines APC.
  • Mettez en file d’attente l’IRP vers un port d’achèvement d’E/S, où il sera exécuté par le thread suivant qui attend le port d’achèvement.

Les threads qui attendent sur un port d’achèvement d’E/S n’attendent pas dans un état alertable. Par conséquent, si ces threads émettent des IRPs qui sont définis pour se terminer en tant qu’API pour le thread, ces achèvements IPC ne se produisent pas en temps opportun ; elles se produisent uniquement si le thread récupère une requête à partir du port d’achèvement des E/S et qu’il entre ensuite une attente alertable.

Utilisation d’un minuteur d’attente avec un appel de procédure asynchrone