Partager via


Implémentation du modèle asynchrone basé sur des événements

Si vous écrivez une classe avec certaines opérations pouvant entraîner d'importants retards, pensez à lui affecter des fonctionnalités asynchrones en implémentant Vue d'ensemble du modèle asynchrone basé sur des événements.

Le modèle asynchrone basé sur des événements fournit une méthode standardisée pour empaqueter une classe ayant des fonctionnalités asynchrones. Si elle est implémentée avec des classes d'assistance telles que AsyncOperationManager, votre classe fonctionnera correctement sous tous les modèles d'application, notamment ASP.NET, les applications console et les applications Windows Forms.

Pour obtenir un exemple qui implémente le modèle asynchrone basé sur des événements, consultez Comment : implémenter un composant qui prend en charge le modèle asynchrone basé sur des événements.

Pour les opérations asynchrones simples, vous trouverez peut-être le composant BackgroundWorker approprié. Pour plus d'informations sur BackgroundWorker, consultez Comment : exécuter une opération en arrière-plan.

La liste suivante décrit les fonctionnalités du modèle asynchrone basé sur des événements abordées dans cette rubrique.

  • Possibilités d'implémentation du modèle asynchrone basé sur des événements

  • Affectation de noms aux méthodes asynchrones

  • Prise en charge éventuelle de l'annulation

  • Prise en charge éventuelle de la propriété IsBusy

  • Prise en charge éventuelle du rapport de progression

  • Prise en charge éventuelle du retour de résultats incrémentiels

  • Gestion des paramètres Out et Ref dans les méthodes

Possibilités d'implémentation du modèle asynchrone basé sur des événements

Envisagez l'implémentation du modèle asynchrone basé sur des événements lorsque :

  • les clients de votre classe n'ont pas besoin des objets WaitHandle et IAsyncResult disponibles pour les opérations asynchrones, ce qui signifie que l'interrogation et WaitAll ou WaitAny devront être développés par le client ;

  • vous souhaitez que les opérations asynchrones soient gérées par le client avec le modèle d'événement/de délégué connu.

Toutes les opérations sont candidates pour une implémentation asynchrone, mais vous devez prendre en considération celles qui, selon vous, entraîneront des latences importantes. Les opérations qui conviennent tout particulièrement sont celles où les clients appellent une méthode et sont avertis de l'achèvement, sans autre intervention nécessaire. Les opérations qui s'exécutent en continu et informent périodiquement les clients de la progression, des résultats incrémentiels ou des changements d'état, sont également appropriées.

Pour plus d'informations sur le choix du moment adéquat pour prendre en charge le modèle asynchrone basé sur des événements, consultez Choix du moment auquel implémenter le modèle asynchrone basé sur les événements.

Affectation de noms aux méthodes asynchrones

Pour chaque méthode synchrone NomMéthode pour laquelle vous souhaitez fournir un équivalent asynchrone :

Définissez une méthode NomMéthodeAsync qui :

  • retourne void ;

  • accepte les mêmes paramètres que la méthode NomMéthode ;

  • accepte les appels multiples.

Définissez éventuellement une surcharge NomMéthodeAsync, identique à NomMéthodeAsync, mais avec un paramètre objet supplémentaire appelé userState. Effectuez cette opération si vous êtes prêt à gérer plusieurs appels simultanés de votre méthode, auquel cas la valeur userState sera restituée à tous les gestionnaires d'événements pour distinguer les appels de la méthode. Vous pouvez également décider d'effectuer cette opération pour stocker l'état utilisateur en vue d'une récupération ultérieure.

Pour chaque signature de méthode NomMéthodeAsync séparée :

  1. Définissez l'événement suivant dans la même classe que la méthode :

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Définissez le délégué suivant et AsyncCompletedEventArgs. Ils seront probablement définis hors de la classe elle-même, mais dans le même espace de noms.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender, 
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Assurez-vous que la classe NomMéthodeCompletedEventArgs expose ses membres en tant que propriétés en lecture seule et non en tant que champs, car les champs empêchent la liaison de données.

    • Ne définissez pas de classes dérivées de AsyncCompletedEventArgs pour les méthodes qui ne produisent pas de résultats. Utilisez simplement une instance de AsyncCompletedEventArgs.

      RemarqueRemarque

      Il est parfaitement acceptable, lorsque cela est réalisable et approprié, de réutiliser le délégué et les types AsyncCompletedEventArgs.Dans ce cas, l'affectation de noms ne sera pas aussi cohérente avec le nom de la méthode, dans la mesure où un délégué donné et AsyncCompletedEventArgs ne seront pas liés à une même méthode.

Prise en charge éventuelle de l'annulation

Si votre classe prend en charge l'annulation d'opérations asynchrones, l'annulation doit être exposée au client comme décrit ci-dessous. Notez que deux points importants doivent être décidés avant de définir la prise en charge de l'annulation :

  • Votre classe, y compris les ajouts anticipés qui y seront faits, possède-t-elle une seule opération asynchrone prenant en charge l'annulation ?

  • Les opérations asynchrones prenant en charge l'annulation peuvent-elles prendre en charge plusieurs opérations en attente ? Autrement dit, la méthode NomMéthodeAsync accepte-t-elle un paramètre userState et autorise-t-elle les appels multiples avant d'attendre que l'un d'eux se termine ?

Utilisez les réponses à ces deux questions dans le tableau ci-dessous pour déterminer le type de signature de votre méthode d'annulation.

Visual Basic

 

Plusieurs opérations simultanées prises en charge

Une seule opération à la fois

Une opération Async dans la classe entière

Sub MethodNameAsyncCancel(ByVal userState As Object)
Sub MethodNameAsyncCancel()

Plusieurs opérations Async dans la classe

Sub CancelAsync(ByVal userState As Object)
Sub CancelAsync()

C#

 

Plusieurs opérations simultanées prises en charge

Une seule opération à la fois

Une opération Async dans la classe entière

void MethodNameAsyncCancel(object userState);
void MethodNameAsyncCancel();

Plusieurs opérations Async dans la classe

void CancelAsync(object userState);
void CancelAsync();

Si vous définissez la méthode CancelAsync(object userState) , les clients doivent être prudents lorsqu'ils choisissent leurs valeurs d'état afin qu'elles soient capables de distinguer toutes les méthodes asynchrones appelées sur l'objet, et pas seulement tous les appels d'une seule méthode asynchrone.

La décision de nommer la version de l'opération asynchrone simple NomMéthodeAsyncCancel est motivée par la capacité à découvrir plus facilement la méthode dans un environnement de design comme IntelliSense de Visual Studio. Cette opération permet de regrouper les membres associés et de les distinguer des autres membres qui ne sont pas concernés par les fonctionnalités asynchrones. Si vous pensez que des opérations asynchrones supplémentaires sont susceptibles d'être ajoutées dans les versions ultérieures, il est préférable de définir CancelAsync.

Ne définissez pas plusieurs méthodes du tableau ci-dessus dans la même classe. Cette opération est inutile ou risque d'encombrer l'interface de classes d'une prolifération de méthodes.

En général, ces méthodes sont retournées immédiatement et l'opération peut ou non être réellement annulée. Dans le gestionnaire d'événements de l'événement NomMéthodeCompleted, l'objet NomMéthodeCompletedEventArgs contient un champ Cancelled que les clients peuvent utiliser pour déterminer si l'annulation a eu lieu.

Respectez la sémantique d'annulation décrite dans Meilleures pratiques pour implémenter le modèle asynchrone basé sur des événements.

Prise en charge éventuelle de la propriété IsBusy

Si votre classe ne prend pas en charge plusieurs appels simultanés, exposez une propriété IsBusy. Cela permet aux développeurs de déterminer si une méthode NomMéthodeAsync s'exécute sans intercepter d'exception de la méthode NomMéthodeAsync.

Respectez la sémantique IsBusy décrite dans Meilleures pratiques pour implémenter le modèle asynchrone basé sur des événements.

Prise en charge éventuelle du rapport de progression

Il est souvent souhaitable de signaler la progression d'une opération asynchrone pendant l'exécution. Pour ce faire, reportez-vous aux indications du modèle asynchrone basé sur des événements.

  • Définissez éventuellement un événement qui sera déclenché par l'opération asynchrone et appelé sur le thread approprié. L'objet ProgressChangedEventArgs comporte un indicateur de progression de valeurs entières qui doit être compris entre 0 et 100.

  • Nommez cet événement comme suit :

    • ProgressChanged si la classe possède plusieurs opérations asynchrones (ou est censée s'agrandir pour inclure plusieurs opérations asynchrones dans les versions ultérieures) ;

    • MethodName ProgressChanged si la classe a une opération asynchrone unique.

    Ce choix de désignation correspond à celui qui est fait pour la méthode d'annulation, comme décrit dans la section Prise en charge éventuelle de l'annulation.

Cet événement doit utiliser la signature de délégué ProgressChangedEventHandler et la classe ProgressChangedEventArgs. Toutefois, si un indicateur de progression plus spécifique au domaine peut être fourni (par exemple, les octets lus et le nombre total d'octets pour une opération de téléchargement), vous devez définir une classe dérivée de ProgressChangedEventArgs.

Notez qu'il existe un seul événement ProgressChanged ou NomMéthodeProgressChanged pour la classe, quel que soit le nombre de méthodes asynchrones qu'elle prend en charge. Les clients sont censés utiliser l'objet userState qui est passé aux méthodes NomMéthodeAsync pour distinguer les mises à jour de progression sur les opérations simultanées multiples.

Dans certaines situations, plusieurs opérations peuvent prendre en charge la progression et retourner chacune un indicateur différent pour la progression. Dans ce cas, un seul événement ProgressChanged n'est pas approprié, et vous pouvez envisager la prise en charge de plusieurs événements ProgressChanged. Dans ce cas, utilisez un modèle d'affectation de nom NomMéthodeProgressChanged pour chaque méthode NomMéthodeAsync.

Respectez la sémantique de rapport de progression décrite dans Meilleures pratiques pour implémenter le modèle asynchrone basé sur des événements.

Prise en charge éventuelle du retour de résultats incrémentiels

Parfois, une opération asynchrone peut retourner des résultats incrémentiels avant la fin de l'opération. De nombreuses options peuvent être utilisées pour prendre en charge ce scénario. Voici quelques exemples.

Classe à une seule opération

Si votre classe prend en charge une seule opération asynchrone et que cette opération peut retourner des résultats incrémentiels :

  • Étendez le type ProgressChangedEventArgs pour intégrer les données de résultat incrémentiel et définissez un événement NomMéthodeProgressChanged avec ces données étendues.

  • Déclenchez cet événement NomMéthodeProgressChanged lorsqu'un résultat incrémentiel doit être signalé.

Cette solution s'applique tout particulièrement à une classe d'opération asynchrone simple, car le fait que le même événement retourne des résultats incrémentiels sur « toutes les opérations », comme c'est le cas pour l'événement NomMéthodeProgressChanged, ne pose aucun problème.

Classe à plusieurs opérations avec des résultats incrémentiels homogènes

Dans ce cas, votre classe prend en charge plusieurs méthodes asynchrones, chacune capable de retourner des résultats incrémentiels, et ces résultats incrémentiels possèdent tous le même type de données.

Suivez le modèle décrit ci-dessus pour les classes à une seule opération, étant donné que la même structure EventArgs fonctionnera pour tous les résultats incrémentiels. Définissez un événement ProgressChanged à la place d'un événement NomMéthodeProgressChanged étant donné qu'il s'applique à plusieurs méthodes asynchrones.

Classe à plusieurs opérations avec des résultats incrémentiels hétérogènes

Si votre classe prend en charge plusieurs méthodes asynchrones, chacune retournant un type de données différent, vous devez :

  • séparer votre rapport de résultat incrémentiel de votre rapport de progression ;

  • définir un événement NomMéthodeProgressChanged séparé avec des EventArgs appropriés pour que chaque méthode asynchrone gère les données de résultat incrémentiel de cette méthode ;

appeler ce gestionnaire d'événements sur le thread approprié comme décrit dans Meilleures pratiques pour implémenter le modèle asynchrone basé sur des événements.

Gestion des paramètres Out et Ref dans les méthodes

Bien que l'utilisation des paramètres out et ref soit généralement déconseillée dans le .NET Framework, voici les règles à suivre lorsqu'ils sont présents :

Selon une méthode synchrone NomMéthode :

  • Les paramètres out de NomMéthode ne doivent pas faire partie de NomMéthodeAsync. Ils doivent plutôt faire partie de NomMéthodeCompletedEventArgs avec le même nom que son paramètre équivalent dans NomMéthode (à moins qu'il existe un nom plus approprié).

  • Les paramètres ref de NomMéthode doivent faire partie de NomMéthodeAsync et de NomMéthodeCompletedEventArgs avec le même nom que son paramètre équivalent dans NomMéthode (à moins qu'il existe un nom plus approprié).

Supposons, par exemple :

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

Votre méthode asynchrone et sa classe AsyncCompletedEventArgs se présenteraient comme suit :

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer 
    End Property
    Public ReadOnly Property Arg2() As String 
    End Property
    Public ReadOnly Property Arg3() As String 
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

Voir aussi

Tâches

Comment : implémenter un composant qui prend en charge le modèle asynchrone basé sur des événements

Comment : exécuter une opération en arrière-plan

Comment : implémenter un formulaire qui utilise une opération d'arrière-plan

Référence

ProgressChangedEventArgs

AsyncCompletedEventArgs

Concepts

Choix du moment auquel implémenter le modèle asynchrone basé sur les événements

Meilleures pratiques pour implémenter le modèle asynchrone basé sur des événements

Autres ressources

Programmation multithread avec le modèle asynchrone basé sur les événements