Loupe

Astuce : optimiser async/await avec ConfigureAwait

 

Si comme moi vous pensez que l’ajout d’async/await dans le framework .NET est une grande avancée, vous allez surement apprécié cette astuce d’optimisation des performances. Nous allons traiter de la méthode ConfigureAwait portée par les Tasks.

 

Le problème

Lorsque vous faites une attente sur une task avec await, il se passe plusieurs choses (je résume) :

  • le contexte appelant est capturé,
  • la task est exécutée ou passe en erreur,
  • On repasse sur le contexte appelant,
  • La suite de la méthode appelante est exécutée

 

public async Task FaireQuelqueChose()
{
    // Le contexte est capturé

    //On appelle la méthode 
    var res = await JeFaisQuelqueChoseAsync();

    //La suite est executée
      
}

 

Cela est super pratique lorsque vous êtes dans une application XAML car cela permet de faire des appels à vos services, à une API et de revenir directement sur le ThreadUI pour modifier l’interface. Imaginons maintenant que vous êtes en train d’écrire une librairie (ou un service de votre application Windows Store par exemple) et que vous faites plusieurs appels consécutifs :

public async Task FaireQuelqueChose()
{
   // Le contexte est capturé

   //On appelle plusieurs méthodes
   var res1 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res2 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res3 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res4 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res5 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res6 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res7 = await JeFaisQuelqueChoseAsync();

   //Retour sur et capture du contexte
   var res8 = await JeFaisQuelqueChoseAsync();
         
}

 

Si vous appelez cette méthode depuis votre thread UI, vous allez le surcharger de capture et de retour sur lui même. De plus, votre service ne sera pas forcément “performant” car il devra attendre que ce thread UI soit libre avant de pouvoir continuer. Et tout cela pour pas grand chose car vous êtes dans un service qui ne touche pas à votre interface graphique.

 

La solution

La solution est toute simple : il suffit d’appeler la méthode ConfigureAwait en passant le paramètre “false” au moment ou vous faite l’appel. Cela désactivera la capture du contexte.  Le reste de la méthode sera potentiellement (ce n’est pas garanti) sur un autre thread du thread pool sans avoir à replacer le contexte. Attention donc si vous utilisez cela dans vos ViewModel, vous ne reviendrez pas sur le thread UI.

public async Task FaireQuelqueChose()
{

   var res1 = await JeFaisQuelqueChoseAsync().ConfigureAwait(false);

   //sur un thread du thread pool
   var res2 = await JeFaisQuelqueChoseAsync().ConfigureAwait(false);

   //sur un autre thread du thread pool
   var res3 = await JeFaisQuelqueChoseAsync().ConfigureAwait(false);

   //sur un autre thread du thread pool
   var res4 = await JeFaisQuelqueChoseAsync().ConfigureAwait(false);

}

 

En pratique

Concrètement, pour l’utiliser dans vos projets vous allez faire souvent la même chose :

  • Je fais un appel dans dans mon ViewModel à une méthode async. Je n’utilise pas ConfigureAwait car je veux capturer le contexte pour revenir sur le thread UI.
  • Dans mon service j’appelle autant de méthodes async que je souhaite et j’utilise ConfigureAwait autant de fois que je veux.
  • En retour de ma méthode, je serais de nouveau sur le thread UI car mon premier appel a capturé le contexte.

 

Une petite astuce simple mais qui peut s’avérer intéressante. Si vous voulez en savoir plus, vous pouvez aussi aller visionner la session des Techdays de Bruno Boucard.

PS : merci Simon et Max pour la relecture.

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus