Partager via


Meilleures pratiques pour l’implémentation du modèle asynchrone basé sur les événements

Le modèle asynchrone basé sur les événements vous offre un moyen efficace d’exposer le comportement asynchrone dans les classes, avec une sémantique d’événement et de délégué familière. Pour implémenter un modèle asynchrone basé sur des événements, vous devez respecter certaines exigences comportementales spécifiques. Les sections suivantes décrivent les exigences et les instructions à prendre en compte lorsque vous implémentez une classe qui suit le modèle asynchrone basé sur les événements.

Pour obtenir une vue d’ensemble, consultez Implémentation du modèle asynchrone basé sur les événements.

Garanties comportementales requises

Si vous implémentez le modèle asynchrone basé sur les événements, vous devez fournir un certain nombre de garanties pour vous assurer que votre classe se comporte correctement et que les clients de votre classe peuvent s’appuyer sur ce comportement.

Achèvement

Appelez toujours le gestionnaire d’événements MethodNameCompleted lorsque vous terminez avec succès, rencontrez une erreur, ou effectuez une annulation. Les applications ne doivent jamais rencontrer une situation où elles restent inactives et ne se terminent jamais. Une exception à cette règle est si l’opération asynchrone elle-même est conçue afin qu’elle ne se termine jamais.

Événement Completed et classe EventArgs

Pour chaque méthode Async MethodName distincte, appliquez les exigences de conception suivantes :

  • Définissez un événement MethodNameCompleted sur la même classe que la méthode.

  • Définissez une EventArgs classe et un délégué associé pour l’événement MethodNameCompleted qui dérive de la AsyncCompletedEventArgs classe. Le nom de classe par défaut doit être de la forme MethodNameCompletedEventArgs.

  • Vérifiez que la EventArgs classe est spécifique aux valeurs de retour de la méthode MethodName . Lorsque vous utilisez la EventArgs classe, vous ne devez jamais demander aux développeurs de caster le résultat.

    L’exemple de code suivant montre respectivement une bonne et mauvaise implémentation de cette exigence de conception.

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
    DemoType result = (DemoType)(e.Result);
}
  • Ne définissez pas de EventArgs classe pour retourner des méthodes qui retournent void. Utilisez plutôt une instance de la AsyncCompletedEventArgs classe.

  • Vérifiez que vous déclenchez toujours l’événement MethodNameCompleted . Cet événement doit être déclenché quand l'opération s'est terminée correctement, ou bien en cas d'erreur ou d'annulation. Les applications ne doivent jamais rencontrer une situation où elles restent inactives et ne se terminent jamais.

  • Vérifiez que vous interceptez toutes les exceptions qui se produisent dans l’opération asynchrone et affectez l’exception interceptée à la Error propriété.

  • En cas d’erreur lors de la fin de la tâche, les résultats ne doivent pas être accessibles. Lorsque la propriété Error n'est pas null, assurez-vous que l'accès à n'importe quelle propriété dans la structure EventArgs déclenche une exception. Utilisez la RaiseExceptionIfNecessary méthode pour effectuer cette vérification.

  • Modélisez un délai au terme duquel une erreur se produit. Quand un délai arrive à expiration, déclenchez l’événement NomMéthodeCompleted et affectez une TimeoutException à la propriété Error.

  • Si votre classe prend en charge plusieurs appels simultanés, vérifiez que l’événement MethodNameCompleted contient l’objet approprié userSuppliedState .

  • Vérifiez que l’événement MethodNameCompleted est déclenché sur le thread approprié et au moment approprié du cycle de vie de l’application. Pour plus d’informations, consultez la section Threading and Contexts.

Exécution simultanée d’opérations

  • Si votre classe prend en charge plusieurs appels simultanés, permettez au développeur de suivre chaque appel séparément en définissant la surcharge MethodNameAsync qui prend un paramètre d'état à valeur d'objet ou un ID de tâche, appelé userSuppliedState. Ce paramètre doit toujours être le dernier paramètre de la signature de la méthode MethodNameAsync .

  • Si votre classe définit la surcharge MethodNameAsync qui prend un paramètre d’état à valeur d’objet ou un ID de tâche, assurez-vous de suivre la durée de vie de l’opération avec cet ID de tâche, et veillez à le réinjecter dans le gestionnaire d’achèvement. Il existe des classes d’assistance disponibles pour aider. Pour plus d’informations sur la gestion de la concurrence, consultez Guide pratique pour implémenter un composant prenant en charge le modèle asynchrone basé sur les événements.

  • Si votre classe définit la méthode Async MethodName sans paramètre d’état et qu’elle ne prend pas en charge plusieurs appels simultanés, assurez-vous que toute tentative d’appeler MethodNameAsync avant que l’appel async MethodName précédent n’ait terminé génère un InvalidOperationException.

  • En règle générale, ne déclenchez pas d’exception si la méthode Async MethodName sans le userSuppliedState paramètre est appelée plusieurs fois afin qu’il existe plusieurs opérations en attente. Vous pouvez lever une exception quand votre classe ne peut pas explicitement gérer cette situation, mais partez du principe que les développeurs peuvent gérer ces différents rappels impossibles à distinguer.

Accès aux résultats

Rapport de progression

  • Prendre en charge les rapports de progression, si possible. Cela permet aux développeurs de fournir une meilleure expérience utilisateur d’application lorsqu’ils utilisent votre classe.

  • Si vous implémentez un événement ProgressChanged ou MethodNameProgressChanged , vérifiez qu’aucun événement de ce type n’est déclenché pour une opération asynchrone particulière après l’événement MethodNameCompleted de cette opération.

  • Si la norme ProgressChangedEventArgs est en cours de remplissage, assurez-vous que le ProgressPercentage peut toujours être interprété comme un pourcentage. Le pourcentage n’a pas besoin d’être précis, mais il doit représenter un pourcentage. Si votre métrique de rapport de progression doit être autre qu’un pourcentage, dérivez une classe de la ProgressChangedEventArgs classe et laissez ProgressPercentage à 0. Évitez d’utiliser une métrique de création de rapports autre qu’un pourcentage.

  • Vérifiez que l’événement ProgressChanged est déclenché sur le thread approprié et au moment approprié dans le cycle de vie de l’application. Pour plus d’informations, consultez la section Threading and Contexts.

Implémentation d’IsBusy

  • N’exposez pas de IsBusy propriété si votre classe prend en charge plusieurs appels simultanés. Par exemple, les proxys de service Web XML n’exposent pas de IsBusy propriété, car ils prennent en charge plusieurs appels simultanés de méthodes asynchrones.

  • La IsBusy propriété doit retourner true une fois la méthode AsyncMethodName appelée et avant que l’événement MethodNameCompleted ait été déclenché. Sinon, il doit retourner false. Les composants BackgroundWorker et WebClient sont des exemples de classes qui exposent une IsBusy propriété.

Annulation

  • Dans la mesure du possible, prenez en charge l'annulation. Cela permet aux développeurs de fournir une meilleure expérience utilisateur d’application lorsqu’ils utilisent votre classe.

  • Dans le cas de l’annulation, définissez l’indicateur Cancelled dans l’objet AsyncCompletedEventArgs .

  • Vérifiez que toute tentative d’accès au résultat déclenche une InvalidOperationException déclaration indiquant que l’opération a été annulée. Utilisez la AsyncCompletedEventArgs.RaiseExceptionIfNecessary méthode pour effectuer cette vérification.

  • Assurez-vous que les appels d'une méthode d'annulation réussissent toujours et qu'ils ne déclenchent jamais d'exception. En règle générale, un client n’est pas averti de savoir si une opération est réellement annulable à un moment donné et n’est pas avertie de la réussite d’une annulation précédemment émise. Toutefois, l’application reçoit toujours une notification lorsqu’une annulation est réussie, car l’application contribue à la gestion de l’état d’achèvement.

  • Déclenchez l’événement MethodNameCompleted lorsque l’opération est annulée.

Erreurs et exceptions

  • Interceptez toutes les exceptions qui se produisent dans l’opération asynchrone et définissez la valeur de la AsyncCompletedEventArgs.Error propriété sur cette exception.

Thread et contextes

Pour une opération correcte de votre classe, il est essentiel que les gestionnaires d’événements du client soient appelés sur le thread ou le contexte appropriés pour le modèle d’application donné, y compris les applications ASP.NET et Windows Forms. Deux classes d’assistance importantes sont fournies pour vous assurer que votre classe asynchrone se comporte correctement sous n’importe quel modèle d’application : AsyncOperation et AsyncOperationManager.

AsyncOperationManager fournit une méthode, CreateOperationqui retourne un AsyncOperation. Votre méthode MethodNameAsync appelle CreateOperation et votre classe utilise la valeur renvoyée AsyncOperation pour suivre la durée de vie de la tâche asynchrone.

Pour signaler la progression, les résultats incrémentiels et l’achèvement au client, appelez les méthodes Post et OperationCompleted sur le AsyncOperation. AsyncOperation prend en charge le marshaling des appels des gestionnaires d’événements du client en thread ou contexte adéquat.

Remarque

Vous pouvez contourner ces règles si vous souhaitez explicitement aller à l'encontre de la politique du modèle d'application, mais bénéficier quand même des autres avantages de l'utilisation du schéma asynchrone basé sur les événements. Par exemple, vous pouvez souhaiter qu'une classe fonctionnant dans Windows Forms soit libre de threads. Vous pouvez créer une classe threadée libre, tant que les développeurs comprennent les restrictions implicites. Les applications console ne synchronisent pas l’exécution des Post appels. Cela peut entraîner le déclenchement d'événements ProgressChanged dans le désordre. Si vous souhaitez avoir sérialisé l’exécution des Post appels, implémentez et installez une System.Threading.SynchronizationContext classe.

Pour plus d’informations sur l’utilisation AsyncOperation et l’activation de vos opérations asynchrones, consultez AsyncOperationManager.

Lignes directrices

  • Dans l’idéal, chaque appel de méthode doit être indépendant des autres. Vous devez éviter de combiner des appels avec des ressources partagées. Si les ressources doivent être partagées entre les appels, vous devez fournir un mécanisme de synchronisation approprié dans votre implémentation.

  • Les conceptions qui nécessitent que le client implémente la synchronisation sont déconseillées. Par exemple, vous pouvez avoir une méthode asynchrone qui reçoit un objet statique global en tant que paramètre ; plusieurs appels simultanés de cette méthode peuvent entraîner une altération des données ou des interblocages.

  • Si vous implémentez une méthode avec la surcharge d’appel multiple (userState dans la signature), votre classe devra gérer une collection d’états utilisateur ou d’ID de tâche et leurs opérations en attente correspondantes. Cette collection doit être protégée avec des régions lock, car les différents appels ajoutent et suppriment des objets userState dans la collection.

  • Envisagez de réutiliser les CompletedEventArgs classes lorsque cela est possible et approprié. Dans ce cas, le nommage n’est pas cohérent avec le nom de la méthode, car un délégué et EventArgs un type donnés ne sont pas liés à une seule méthode. Toutefois, forcer les développeurs à caster la valeur récupérée à partir d'une propriété sur le EventArgs n'est jamais acceptable.

  • Si vous créez une classe qui dérive de Component, n’implémentez pas et installez votre propre SynchronizationContext classe. Les modèles d’application, et non les composants, contrôlent ce SynchronizationContext qui est utilisé.

  • Lorsque vous utilisez le multithreading de n’importe quelle sorte, vous vous exposez potentiellement à des bogues très graves et complexes. Avant d’implémenter une solution qui utilise le multithreading, consultez les meilleures pratiques en matière de threads managés.

Voir aussi