Async/Await est sans conteste la fonctionnalité la plus puissante introduite avec C#5. Mais quels sont les pièges à éviter ? Et quels sont les coûts associés à l'utilisation de ces mots-clés ?
L'article "Bonnes pratiques dans le domaine de la programmation asynchrone", sur MSDN, met en lumière les points suivants :
- Toujours préférer les méthodes de type "async Task" aux méthodes "async void", sauf pour les Event Handlers. Ces deux types de méthodes ont des sémantiques différentes en matière de gestion des erreurs. Keith Patton, de Marker Metro explique lui aussi cela en détail.
- Éviter de mélanger du code bloquant et du code non-bloquant. Le mélange peut causer des deadlocks, implique une gestion des erreurs plus complexe et peut provoquer un blocage inattendu du contexte du thread.
- Utiliser ConfigureAwait(false) pour de meilleures performances si le code de continuation n'a pas besoin du contexte d'origine, auquel cas la continuation sera exécutée dans le contexte du thread pool. Cela peut être aussi utile pour éviter des deadlocks dans le cas où on se retrouve forcé de mélanger du code synchrone et du code asynchrone. Notez qu'il existe des nuances à considérer lorsque le code est exécuté côté serveur.
Chris Hurley, développeur chez RedGate, explique le surcoût en termes de sollicitation du CPU lié à l'utilisation d'Async/Await et le démontre en analysant les performances d'une portion de code :
- Appeler une méthode définie avec le mot-clé "async" induit la création d'une machine à état et la construction d'une tâche (Task) contenant le travail à exécuter ainsi la récupération du contexte d'exécution et du contexte de synchronisation.
- Dans l'exemple proposé, 963 méthodes du Framework sont exécutées afin d'initialiser une méthode async relativement simple, lors de son premier appel.
- Le contexte est mis en cache, donc le surcoût des appels suivants est moindre
- Pour les méthodes qui s'exécuteraient de façon synchrone en un temps très court (de l'ordre d'1ms), les constructions supplémentaires pour async bloquent le thread appelant plus longtemps. Dans l'exemple, il faut bien 45ms avant que le thread appelant soit débloqué. Même lorsque ce code est exécuté dans une boucle, bien que les appels suivants présentent moins d'overhead, le thread appelant ne perçoit vraiment aucun gain de performance.
- En conclusion : évitez d'utiliser async/await pour les méthodes très courtes et évitez les instructions await à l'intérieur de boucles courtes (placez plutôt la totalité de la boucle dans une méthode async).
InfoQ a déjà reporté certaines autres embûches liées à l'utilisation de ces mots-clés.