Partager via


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

Si vous écrivez une classe avec certaines opérations susceptibles d’entraîner des retards notables, envisagez de lui donner des fonctionnalités asynchrones en implémentant le modèle asynchrone basé sur les événements.

Le modèle asynchrone basé sur les événements fournit un moyen standardisé de empaqueter une classe qui a des fonctionnalités asynchrones. Si elle est implémentée avec des classes d’assistance telles que AsyncOperationManager, votre classe fonctionne correctement sous n’importe quel modèle d’application, y compris les applications ASP.NET, les applications console et les applications Windows Forms.

Pour obtenir un exemple qui implémente le modèle asynchrone basé sur les événements, consultez Guide pratique pour implémenter un composant prenant en charge le modèle asynchrone basé sur les événements.

Pour les opérations asynchrones simples, vous pouvez trouver le BackgroundWorker composant approprié. Pour plus d’informations sur BackgroundWorker, consultez Guide pratique pour exécuter une opération en arrière-plan.

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

  • Opportunités d’implémentation du modèle asynchrone basé sur les événements

  • Affectation de noms à des méthodes asynchrones

  • Prise en charge facultative de l’annulation

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

  • Fournir éventuellement la prise en charge des rapports d’avancement

  • Fournir éventuellement la prise en charge du retour des résultats incrémentiels

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

Opportunités d’implémentation du modèle asynchrone basé sur les événements

Envisagez d’implémenter le modèle asynchrone basé sur les événements quand :

  • Les clients de votre classe n’ont pas besoin WaitHandle et IAsyncResult les objets disponibles pour les opérations asynchrones, ce qui signifie que l’interrogation et WaitAll ou WaitAny doit être générée par le client.

  • Vous souhaitez que les opérations asynchrones soient gérées par le client avec le modèle d’événement/délégué familier.

Toute opération est candidate à une implémentation asynchrone, mais celles que vous prévoyez d’entraîner de longues latences doivent être prises en compte. Particulièrement approprié, les opérations dans lesquelles les clients appellent une méthode et sont avertis à la fin, sans intervention supplémentaire requise. Il convient également d’effectuer des opérations qui s’exécutent en continu, en informant régulièrement les clients de la progression, des résultats incrémentiels ou des modifications d’état.

Pour plus d’informations sur la prise en charge du modèle asynchrone basé sur les événements, voir Quand implémenter le modèle asynchrone basé sur les événements.

Affectation de noms à des méthodes asynchrones

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

Définissez une méthode Async MethodName qui :

  • Retourne void.

  • Prend les mêmes paramètres que la méthode MethodName .

  • Accepte plusieurs appels.

Définissez éventuellement une surcharge Async MethodName, identique à MethodNameAsync, mais avec un paramètre d’objet supplémentaire appelé userState. Pour ce faire, si vous êtes prêt à gérer plusieurs appels simultanés de votre méthode, auquel cas la userState valeur est remise à tous les gestionnaires d’événements pour distinguer les appels de la méthode. Vous pouvez également choisir de le faire simplement en tant qu’emplacement pour stocker l’état utilisateur pour une récupération ultérieure.

Pour chaque signature de méthode Async MethodName distincte :

  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. Celles-ci seront probablement définies en dehors 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; }
    }
    
    • Vérifiez que la classe MethodNameCompletedEventArgs 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 AsyncCompletedEventArgsaucune classe dérivée pour les méthodes qui ne produisent pas de résultats. Utilisez simplement une instance de AsyncCompletedEventArgs lui-même.

      Remarque

      Il est parfaitement acceptable, lorsque cela est possible et approprié, de réutiliser les délégués et AsyncCompletedEventArgs les types. Dans ce cas, le nommage ne sera pas aussi cohérent avec le nom de la méthode, car un délégué donné et AsyncCompletedEventArgs ne sera pas lié à une seule méthode.

Prise en charge facultative 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. Il existe deux points de décision à atteindre avant de définir votre support d’annulation :

  • Votre classe, y compris les ajouts prévus à venir, n’a-t-elle qu’une seule opération asynchrone qui prend en charge l’annulation ?
  • Les opérations asynchrones qui prennent en charge l’annulation prennent-elles en charge plusieurs opérations en attente ? Autrement dit, la méthode Async MethodName prend-elle un userState paramètre et autorise-t-elle plusieurs appels avant d’attendre la fin d’une opération ?

Utilisez les réponses à ces deux questions dans le tableau ci-dessous pour déterminer la 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 asynchrone dans toute la classe Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
Opérations asynchrones multiples 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 asynchrone dans toute la classe void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
Opérations asynchrones multiples dans la classe void CancelAsync(object userState); void CancelAsync();

Si vous définissez la CancelAsync(object userState) méthode, les clients doivent être prudents lors du choix de leurs valeurs d’état pour les rendre capables de distinguer toutes les méthodes asynchrones appelées sur l’objet, et pas seulement entre tous les appels d’une seule méthode asynchrone.

La décision de nommer la version mono-async-operation MethodNameAsyncCancel est basée sur la possibilité de découvrir plus facilement la méthode dans un environnement de conception comme IntelliSense de Visual Studio. Cela regroupe les membres associés et les distingue des autres membres qui n’ont rien à voir avec les fonctionnalités asynchrones. Si vous pensez qu’il peut y avoir des opérations asynchrones supplémentaires ajoutées dans les versions suivantes, 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. Cela n’aura pas de sens, ou il encombre l’interface de classe avec une prolifération de méthodes.

Ces méthodes retournent généralement immédiatement, et l’opération peut ou non être annulée. Dans le gestionnaire d’événements de l’événement MethodNameCompleted , l’objet MethodNameCompletedEventArgs contient un Cancelled champ, que les clients peuvent utiliser pour déterminer si l’annulation s’est produite.

Respectez la sémantique d’annulation décrite dans Les meilleures pratiques pour l’implémentation du modèle asynchrone basé sur les événements.

Prise en charge facultative de la propriété IsBusy

Si votre classe ne prend pas en charge plusieurs appels simultanés, envisagez d’exposer une IsBusy propriété. Cela permet aux développeurs de déterminer si une méthode MethodNameAsync est en cours d’exécution sans intercepter une exception de la méthode MethodNameAsync .

Respectez la IsBusy sémantique décrite dans Les meilleures pratiques pour l’implémentation du modèle asynchrone basé sur les événements.

Fournir éventuellement la prise en charge des rapports d’avancement

Il est fréquemment souhaitable qu’une opération asynchrone signale la progression pendant son opération. Le modèle asynchrone basé sur les événements fournit des instructions pour ce faire.

  • Définissez éventuellement un événement à lever par l’opération asynchrone et appelé sur le thread approprié. L’objet ProgressChangedEventArgs porte un indicateur de progression à valeur entière qui doit être compris entre 0 et 100.

  • Nommez cet événement comme suit :

    • ProgressChanged si la classe a plusieurs opérations asynchrones (ou devrait augmenter pour inclure plusieurs opérations asynchrones dans les versions ultérieures) ;

    • MethodNameProgressChanged si la classe a une seule opération asynchrone.

    Ce choix de nommage parallèles faits pour la méthode d’annulation, comme décrit dans la section Annulation de prise en charge facultative.

Cet événement doit utiliser la ProgressChangedEventHandler signature de délégué et la ProgressChangedEventArgs classe. Sinon, si un indicateur de progression plus spécifique au domaine peut être fourni (par exemple, les octets lus et le 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 n’existe qu’un ProgressChanged ou un événement ProgressChanged MethodName 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 passé aux méthodes AsyncMethodName pour distinguer les mises à jour de progression sur plusieurs opérations simultanées.

Il peut y avoir des situations dans lesquelles plusieurs opérations prennent en charge la progression et chacun retourne un indicateur différent pour la progression. Dans ce cas, un seul ProgressChanged événement n’est pas approprié et vous pouvez envisager de prendre en charge plusieurs ProgressChanged événements. Dans ce cas, utilisez un modèle d’affectation de noms de MethodNameProgressChanged pour chaque méthode Async MethodName.

Respectez la sémantique de création de rapports de progression décrites dans les meilleures pratiques d’implémentation du modèle asynchrone basé sur les événements.

Fournir éventuellement la prise en charge du retour des résultats incrémentiels

Parfois, une opération asynchrone peut retourner des résultats incrémentiels avant la fin. Il existe plusieurs options qui peuvent être utilisées pour prendre en charge ce scénario. Voici quelques exemples.

Classe à opération unique

Si votre classe prend uniquement en charge une seule opération asynchrone et que cette opération est en mesure de retourner des résultats incrémentiels, puis :

  • Étendez le ProgressChangedEventArgs type pour transporter les données de résultat incrémentielles et définissez un événement MethodNameProgressChanged avec ces données étendues.

  • Déclenchez cet événement MethodNameProgressChanged lorsqu’il existe un résultat incrémentiel à signaler.

Cette solution s’applique spécifiquement à une classe d’opération asynchrone unique, car il n’existe aucun problème avec le même événement qui se produit pour retourner des résultats incrémentiels sur « toutes les opérations », car l’événement MethodNameProgressChanged le fait.

Classe à opérations multiples 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 ont tous le même type de données.

Suivez le modèle décrit ci-dessus pour les classes à opération unique, car la même EventArgs structure fonctionnera pour tous les résultats incrémentiels. Définissez un ProgressChanged événement au lieu d’un événement MethodNameProgressChanged , car il s’applique à plusieurs méthodes asynchrones.

Classe à opérations multiples 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éparez vos rapports de résultats incrémentiels de vos rapports de progression.

  • Définissez un événement MethodNameProgressChanged distinct approprié EventArgs pour chaque méthode asynchrone pour gérer les données de résultat incrémentielles de cette méthode.

Appelez ce gestionnaire d’événements sur le thread approprié, comme décrit dans Best Practices for Implementing the Event-based Asynchrone Pattern.

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

Bien que l’utilisation et outref soit, en général, découragée dans .NET, voici les règles à suivre lorsqu’elles sont présentes :

Étant donné une méthode synchrone , MethodName :

  • out les paramètres de MethodName ne doivent pas faire partie de MethodNameAsync. Au lieu de cela, ils doivent faire partie de MethodNameCompletedEventArgs portant le même nom que son paramètre équivalent dans MethodName (sauf s’il existe un nom plus approprié).

  • refles paramètres de MethodName doivent apparaître dans le cadre d’AsyncMethodName et, dans le cadre de MethodNameCompletedEventArgs portant le même nom que son paramètre équivalent dans MethodName (sauf s’il existe un nom plus approprié).

Par exemple, étant donné :

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 AsyncCompletedEventArgs classe ressemblent à ceci :

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