Analyse du code source d'AsyncAwaitBestPractices : Comprendre les machines d'état asynchrones générées par le compilateur

Principes Fondamentaux des Machines d'État Asynchrones

Les machines d'état asynchrones sont le mécanisme sous-jacent que le compilateur C# utilise pour implémenter la syntaxe async/await. Elles décomposent une méthode asynchrone en une série de transitions d'état, permettant une exécution de code non bloquante. Lorsqu'un développeur écrit une méthode asynchrone comme celle-ci :


public async Task<int> FetchDataAndDoubleAsync()
{
   var rawData = await FetchDataFromSourceAsync();
   return rawData * 2;
}
 

Le compilateur génère automatiquement une structure qui implémente l'interface IAsyncStateMachine. Cette structure contient des champs pour les états, des variables temporaires et une méthode MoveNext, qui gère le flux d'exécution de l'opération asynchrone.

Application dans AsyncAwaitBestPractices

Dans le code source du projet, l'optimisation des machines d'état asynchrones se manifeste principalement dans plusieurs composants clés :

1. Implémentation Sécurisée des Commandes Asynchrones

Le module MVVM du projet propose les classes AsyncCommand et AsyncValueCommand. Ces commandes utilisent des machines d'état en interne pour gérer le cycle de vie des opérations asynchrones. Par exemple, dans le fichier src/AsyncAwaitBestPractices.MVVM/AsyncCommand/AsyncCommand.shared.cs, la méthode d'exécution de la commande suit le modèle asynchrone standard :


public async ValueTask ExecuteAsync(TCommandParameter parameter)
{
   if (_isCurrentlyExecuting)
       return;

   try
   {
       _isCurrentlyExecuting = true;
       // Notifier le changement d'état (par exemple, pour le CanExecute)
       OnCanExecuteChanged?.Invoke(this, EventArgs.Empty);

       await _commandAction(parameter).ConfigureAwait(false);
   }
   finally
   {
       _isCurrentlyExecuting = false;
       // Notifier à nouveau le changement d'état
       OnCanExecuteChanged?.Invoke(this, EventArgs.Empty);
   }
}
 

Ce code, grâce à la machine d'état générée, gère automatiquement la sauvegarde et la restauration de l'état avant et après l'instruction await, garantissant ainsi la sécurité du thread UI et la cohérence de l'état de la commande.

2. Modèle "Fire-and-Forget" Sécurisé contre les Exceptions

Dans le fichier src/AsyncAwaitBestPractices/SafeFireAndForgetExtensions.shared.cs, le projet fournit des méthodes d'extension sécurisées pour le modèle "Fire-and-Forget". Ces méthodes utilisent la machine d'état pour capturer les exceptions survenant lors des opérations asynchrones :


public static void ForgetSafely(this Task asyncOperation, Action<Exception>? onError = null)
{
   // La machine d'état gère les exceptions asynchrones automatiquement
   _ = asyncOperation.ContinueWith(task =>
   {
       if (task.IsFaulted && onError != null)
       {
           onError(task.Exception.InnerException ?? task.Exception);
       }
   }, TaskContinuationOptions.OnlyOnFaulted);
}
 

Cette implémentation évite les risques d'écrasement d'application liés aux méthodes async void traditionnelles, tout en maintenant l'efficacité d'exécution de la machine d'état asynchrone.

Bonnes Pratiquse d'Optimisation des Machines d'État

En analysant le code de test du projet, nous pouvons dégager des techniques d'optimisation pour les machines d'état asynchrones :

1. Éviter la Complexité Excessive des Machines d'État

Dans le fichier sample/ViewModels/NewsViewModel_GoodAsyncAwaitPractices.cs, les bonnes pratiques illustrent comment la décomposition de longues méthodes asynchrones réduit la complexité de la machine d'état générée :


private async Task RefreshDataAsync(CancellationToken cancellationToken)
{
   IsLoadingIndicatorVisible = true;
   try
   {
       var topStoryIdentifiers = await FetchTopStoryIdsAsync(cancellationToken);
       var detailedStories = await FetchStoriesDetailsAsync(topStoryIdentifiers, cancellationToken);
       DisplayedStories = new ObservableCollection<StoryModel>(detailedStories);
   }
   finally
   {
       IsLoadingIndicatorVisible = false;
   }
}
 

2. Utilisation Judicieuse de ValueTask pour Réduire les Allocations

Le projet fait un usage intensif de ValueTask pour optimiser les performances, comme le montrent les méthodes de test dans src/AsyncAwaitBestPractices.UnitTests/BaseTest.cs :


protected static async ValueTask SimulateShortDelayAsync()
{
   await Task.Delay(ShortDelayDuration);
}
 

ValueTask réduit les allocations inutiles sur le tas, allégeant ainsi la pression mémoire sur la machine d'état, particulièrement bénéfique pour les méthodes asynchrones appelées fréquemment.

Techniques de Débogage et d'Analyse des Machines d'État

Pour une compréhension approfondie du fonctionnement des machines d'état asynchrones, les méthodes suivantes sont utiles :

  1. Inspection du code IL : Utilisez des outils de décompilation pour examiner le code IL généré pour les machines d'état des méthodes async.
  2. Points d'arrêt : Placez des points d'arrêt avant et après les instructions await pour observer les transitions d'état de la machine.
  3. Outils de diagnostic : Employez la fenêtre de diagnostic asynchrone de Visual Studio pour suivre le flux d'exécution de la machine d'état.

Ces techniques peuvant éclairer la mise en œuvre de scénarios asynchrones complexes, tels que ceux rencontrés dans src/AsyncAwaitBestPractices.UnitTests/WeakEventManagerTests.

Conclusion

Le projet AsyncAwaitBestPractices exploite astucieusement les machines d'état asynchrones générées par le compilateur C# pour un traitement efficace et sécurisé des opérations asynchrones. La maîtrise du fonctionnement des machines d'état est essentielle non seulement pour utiliser cette bibliothèque, mais aussi pour écrire du code asynchrone plus performant. Que ce soit pour l'implémentation de commandes MVVM ou le modèle "Fire-and-Forget", la machine d'état est le moteur central de la programmation asynchrone, et sa compréhension améliorera considérablement vos compétences.

En étudiant les implémentations dans les modules src/AsyncAwaitBestPractices.MVVM et sample/ViewModels du projet, les développeurs peuvent assimiler les meilleures pratiques relatives aux machines d'état asynchrones, éviter les pièges courants et produire des applications .NET plus robustes et performantes.

Étiquettes: C# async/await machine d'état asynchrone optimisation .NET ValueTask

Publié le 25 juin à 19h36