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

Le modèle asynchrone basé sur des événements vous fournit une méthode efficace pour exposer un comportement asynchrone dans les classes, avec une sémantique de délégué et d'événement connue. Pour implémenter le modèle asynchrone basé sur des événements, vous devez suivre des spécifications comportementales particulières. Les sections suivantes décrivent les spécifications et les indications que vous devez prendre en compte lors de l'implémentation d'une classe qui suit le modèle asynchrone basé sur des événements.

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

La liste suivante présente les meilleures pratiques décrites dans cette rubrique :

  • Garanties comportementales requises

  • Achèvement

  • Événement terminé et EventArgs

  • Exécution simultanée d'opérations

  • Accès aux résultats

  • Rapport de progression

  • Implémentation de IsBusy

  • Annulation

  • Erreurs et exceptions

  • Threads et contextes

  • Indications

Garanties comportementales requises

Si vous implémentez le modèle asynchrone basé sur des événements, vous devez fournir plusieurs garanties pour certifier que votre classe se comportera correctement et que les clients de votre classe peuvent se fier à un tel comportement.

Achèvement

Appelez toujours le gestionnaire d'événements MethodNameCompleted dans le cas d'un achèvement correct, d'une erreur ou d'une annulation. Les applications ne doivent jamais se trouver dans une situation où elles restent inactives et où l'achèvement n'a jamais lieu. Le cas où l'opération asynchrone elle-même est conçue de façon à ne jamais se terminer fait exception à cette règle.

Événement terminé et EventArgs

Pour chaque méthode MethodNameAsync séparée, appliquez les spécifications de design suivantes :

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

  • Définissez une classe EventArgs et le délégué qui l'accompagne pour l'événement MethodNameCompleted dérivant de la classe AsyncCompletedEventArgs. Le nom de classe par défaut doit avoir le format MethodNameCompletedEventArgs.

  • Assurez-vous que la classe EventArgs est spécifique aux valeurs de retour de la méthode MethodName. Lorsque vous utilisez la classe EventArgs, vous ne devez jamais attendre des développeurs qu'ils effectuent un cast du résultat.

    L'exemple de code suivant illustre respectivement une bonne et une mauvaise implémentation de cette spécification de design.

[C#]

// 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 une classe EventArgs pour retourner des méthodes qui retournent void. À la place, utilisez une instance de la classe AsyncCompletedEventArgs.

  • Assurez-vous de toujours déclencher l'événement MethodNameCompleted. Cet événement doit être déclenché en cas de réussite, d'erreur ou d'annulation. Les applications ne doivent jamais se trouver dans une situation où elles restent inactives et où l'achèvement n'a jamais lieu.

  • Veillez à intercepter toutes les exceptions qui se produisent dans l'opération asynchrone et à assigner l'exception interceptée à la propriété Error.

  • Si une erreur s'est produite au cours de l'exécution 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 à une propriété de la structure EventArgs lève une exception. Utilisez la méthode RaiseExceptionIfNecessary pour effectuer cette vérification.

  • Modéliser un délai d'expiration en tant qu'erreur. Lorsqu'un délai d'expiration se produit, déclenchez l'événement MethodNameCompleted et assignez une TimeoutException à la propriété Error.

  • Si votre classe prend en charge plusieurs appels simultanés, assurez-vous que l'événement MethodNameCompleted contient l'objet userSuppliedState approprié.

  • Assurez-vous 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 Threads et contextes.

Exécution simultanée d'opérations

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

  • Si votre classe définit la surcharge MethodNameAsync qui accepte un paramètre d'état de valeurs d'objet ou l'ID de la tâche, vous devez assurer le suivi de la durée de vie de l'opération avec cet ID de tâche et le restituer au gestionnaire d'achèvement. Des classes d'assistance sont à votre disposition. Pour plus d'informations sur la gestion de l'accès concurrentiel, consultez Procédure pas à pas : implémentation d'un composant qui prend en charge le modèle asynchrone basé sur des événements.

  • Si votre classe définit la méthode MethodNameAsync sans le paramètre d'état et qu'elle ne prend pas en charge plusieurs appels simultanés, assurez-vous que toute tentative d'appel de MethodNameAsync avant la fin de l'appel précédent de MethodNameAsync lève une InvalidOperationException.

  • En général, ne levez pas d'exception si la méthode MethodNameAsync sans le paramètre userSuppliedState est appelée plusieurs fois de sorte que plusieurs opérations sont en attente. Vous pouvez lever une exception lorsque votre classe ne peut pas régler explicitement cette situation, mais vous supposez que les développeurs peuvent gérer ces multiples rappels impossibles à distinguer.

Accès aux résultats

Rapport de progression

  • Si possible, prenez en charge le rapport de progression. Cela permet aux développeurs de fournir une meilleure expérience aux utilisateurs de l'application lorsqu'ils utilisent votre classe.

  • Si vous implémentez un événement ProgressChanged/MethodNameProgressChanged, assurez-vous qu'aucun événement de ce type n'est déclenché pour une opération asynchrone spécifique après le déclenchement de l'événement MethodNameCompleted de cette opération.

  • Si le ProgressChangedEventArgs standard est rempli, assurez-vous que le ProgressPercentage peut toujours être interprété comme un pourcentage. Le pourcentage n'a pas besoin d'être exact mais il doit représenter un pourcentage. Si votre métrique de rapport de progression ne doit pas être un pourcentage, dérivez une classe de la classe ProgressChangedEventArgs et conservez ProgressPercentage à 0. Évitez d'utiliser une métrique de rapport autre qu'un pourcentage.

  • Assurez-vous que l'événement ProgressChanged 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 Threads et contextes.

Implémentation de IsBusy

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

  • La propriété IsBusy doit retourner la valeur true après l'appel de la méthode MethodNameAsync et avant que l'événement MethodNameCompleted ne soit déclenché. Sinon, elle doit retourner la valeur false. Les composants BackgroundWorker et WebClient sont des exemples de classes qui exposent une propriété IsBusy.

Annulation

  • Si possible, prenez en charge l'annulation. Cela permet aux développeurs de fournir une meilleure expérience aux utilisateurs de l'application lorsqu'ils utilisent votre classe.

  • En cas d'annulation, définissez l'indicateur Cancelled dans l'objet AsyncCompletedEventArgs.

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

  • Assurez-vous que les appels à une méthode d'annulation sont toujours retournés avec succès et qu'ils ne lèvent jamais d'exception. En général, un client n'est pas averti de la possibilité d'annuler une opération à un moment donné ou de l'aboutissement d'une annulation précédemment émise. Toutefois, l'application est toujours informée lorsqu'une annulation aboutit car l'application participe à 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 affectez la valeur de la propriété AsyncCompletedEventArgs.Error à cette exception.

Threads et contextes

Pour un fonctionnement correct de votre classe, il est essentiel que les gestionnaires d'événements du client soient appelés sur le thread ou le contexte approprié pour le modèle d'application donné, y compris les applications ASP.NET et Windows Forms. Deux classes d'assistance importantes sont fournies pour garantir que votre classe asynchrone se comporte correctement sous tout modèle d'application : AsyncOperation et AsyncOperationManager.

AsyncOperationManager fournit une méthode, CreateOperation, qui retourne une AsyncOperation. Votre méthode MethodNameAsync appelle CreateOperation et votre classe utilise l'AsyncOperation retournée pour assurer le suivi de la durée de vie de la tâche asynchrone.

Pour signaler la progression, des résultats incrémentiels et la fin au client, appelez les méthodes Post et OperationCompleted sur AsyncOperation. AsyncOperation est chargé de marshaler les appels aux gestionnaires d'événements du client au thread ou contexte approprié.

RemarqueRemarque

Vous pouvez contourner ces règles si vous souhaitez explicitement aller à l'encontre de la stratégie du modèle d'application mais que vous souhaitez toujours profiter des autres avantages de l'utilisation du modèle asynchrone basé sur des événements.Par exemple, vous souhaitez peut-être qu'une classe fonctionnant dans les Windows Forms soit libre de thread.Vous pouvez créer une classe libre de thread tant que les développeurs comprennent les restrictions impliquées.Les applications console ne synchronisent pas l'exécution des appels Post.Cela peut provoquer le déclenchement des événements ProgressChanged de manière désordonnée.Si vous souhaitez que l'exécution des appels Post soit sérialisée, implémentez et installez une classe System.Threading.SynchronizationContext.

Pour plus d'informations sur l'utilisation de AsyncOperation et AsyncOperationManager pour activer vos opérations asynchrones, consultez Procédure pas à pas : implémentation d'un composant qui prend en charge le modèle asynchrone basé sur des événements.

Indications

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

  • Les designs qui demandent au client d'implémenter la synchronisation sont déconseillés. Par exemple, vous pouvez avoir une méthode asynchrone qui reçoit un objet statique global comme paramètre ; plusieurs appels simultanés d'une telle méthode peuvent entraîner l'altération des données ou des interblocages.

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

  • Envisagez la réutilisation des classes CompletedEventArgs lorsque cela est possible et approprié. Dans ce cas, l'affectation de nom n'est pas cohérente avec le nom de la méthode car un délégué et un type EventArgs donnés ne sont pas liés à une seule méthode. Toutefois, il n'est pas acceptable d'obliger les développeurs à effectuer un cast de la valeur récupérée d'une propriété sur EventArgs.

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

  • Lorsque vous utilisez le multithreading de tout type, vous vous exposez potentiellement à des bogues très graves et complexes. Avant d'implémenter une solution utilisant le multithreading, consultez Meilleures pratiques pour le threading managé.

Voir aussi

Tâches

Comment : utiliser des composants qui prennent en charge le modèle asynchrone basé sur des événements

Procédure pas à pas : implémentation d'un composant qui prend en charge le modèle asynchrone basé sur des événements

Référence

AsyncOperation

AsyncOperationManager

AsyncCompletedEventArgs

ProgressChangedEventArgs

BackgroundWorker

Concepts

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

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