L’utilisation d’Async-Wait peut-elle vous apporter des avantages en termes de performances?

Chaque fois que je lis des informations sur asyncawait , l’exemple de cas d’utilisation correspond toujours à une interface utilisateur que vous ne souhaitez pas figer. Tous les manuels / didacticiels de programmation sont identiques ou bien le blocage de l’interface utilisateur est le seul cas asyncawait que je devrais le savoir en tant que développeur.

Existe-t-il des exemples d’utilisation asyncawait à améliorer les performances d’un algorithme? Comme par exemple, prenons l’une des questions classiques d’entrevue de programmation:

  • Trouver l’ancêtre commun le plus proche dans un arbre binary
  • Étant donné a[0] , a[1] , …, a[n-1] représentant les chiffres d’un nombre en base 10, trouvez le prochain nombre le plus élevé qui utilise les mêmes chiffres
  • Trouvez la médiane de deux tableaux sortingés (c’est-à-dire le nombre médian si vous deviez les fusionner)
  • Étant donné un tableau de nombres 1 , 2 , …, n avec un chiffre manquant, trouver le nombre manquant
  • Trouver les 2 plus grands nombres d’un tableau

Existe-t-il un moyen de faire en sorte que ceux qui utilisent asyncawait avec des avantages en await de performances? Et si oui, que faire si vous n’avez qu’un processeur? Dans ce cas, votre machine ne se limite-t-elle pas à diviser son temps en tâches plutôt qu’à les exécuter en même temps?

Dans cette interview, Eric Lippert a comparé l’attente asynchrone avec un cuisinier préparant le petit-déjeuner . Cela m’a beaucoup aidé à comprendre les avantages de l’attente asynchrone. Cherchez quelque part au milieu pour ‘async-wait’

Supposons qu’un cuisinier doive préparer le petit-déjeuner. Il doit gridr du pain et cuire des œufs, peut-être aussi faire du thé?

Méthode 1: synchrone. Effectué par un fil . Vous commencez à gridr le pain. Attendez que le pain soit grillé. Enlevez le pain. Commencez à faire bouillir de l’eau, attendez que l’eau bout et insérez votre oeuf. Attendez que l’oeuf soit prêt et retirez-le. Commencez à faire bouillir de l’eau pour le thé. Attendez que l’eau soit bouillie et faites le thé.

Vous voyez toutes les attentes. tant que le fil attend, il peut faire autre chose.

Méthode 2: Async-wait, toujours un fil. Vous commencez à faire gridr le pain. Pendant que le pain est grillé, vous commencez à faire bouillir de l’eau pour les œufs et pour le thé. Ensuite, vous commencez à attendre. Lorsque l’une des trois tâches est terminée, vous effectuez la deuxième partie, en fonction de la tâche terminée en premier. Donc, si l’eau pour les œufs bout tout d’abord, vous faites cuire les œufs et attendez à nouveau que toutes les tâches soient terminées.

Dans cette description, une seule personne (vous) fait tout le travail. Un seul thread est impliqué. La bonne chose est que, comme il n’y a qu’un seul thread qui fait le travail, le code semble assez synchrone au lecteur et il n’y a pas grand-chose de nécessité de sécuriser vos variables thread.

Il est facile de voir que de cette façon votre petit-déjeuner sera prêt plus rapidement (et que votre pain sera encore chaud!). Dans la vie de l’ordinateur, cela se produira lorsque votre thread devra attendre la fin d’un autre processus, comme écrire un fichier sur un disque, obtenir des informations d’une firebase database ou d’Internet. Ce sont généralement le type de fonctions où une version asynchrone est disponible: Write et WriteAsync , Read et ReadAsync .

Ajout: après quelques remarques d’autres utilisateurs ailleurs et quelques tests, j’ai trouvé que c’est en fait tout fil qui continue votre travail après l’attente. Cet autre fil a le même “contexte” et peut donc agir comme s’il s’agissait du fil d’origine.

Méthode 3: embaucher des cuisiniers pour faire gridr le pain et faire bouillir les œufs pendant que vous préparez le thé: véritable asynchrone. Plusieurs threads C’est l’option la plus chère, car elle implique la création de threads séparés. Dans l’exemple de la préparation du petit-déjeuner, le processus ne sera probablement pas beaucoup plus rapide car, du moment que le processus est relativement long, vous ne faites rien de toute façon. Mais si, par exemple, vous devez également couper des tomates en tranches, il peut être utile de laisser un cuisinier (fil séparé) le faire pendant que vous faites les autres tâches en utilisant asynchrone. Bien sûr, l’une des choses que vous devez faire est d’attendre que le cuisinier termine son tranchage.

Async and Await est un autre article qui explique beaucoup de choses, écrit par le très utile Stephen Cleary.

Chaque fois que je lis des informations sur async-wait, l’exemple d’utilisation est toujours celui dans lequel il existe une interface utilisateur que vous ne souhaitez pas figer.

C’est le cas d’utilisation le plus courant pour async . L’autre est dans les applications côté serveur, où async peut augmenter l’évolutivité des serveurs Web.

Existe-t-il des exemples d’utilisation asynchrone pour optimiser les performances d’un algorithme?

Non.

Vous pouvez utiliser la bibliothèque de tâches parallèle si vous souhaitez effectuer un parallel processing. Le parallel processing consiste à utiliser plusieurs threads, ce qui divise des parties d’un algorithme entre plusieurs cœurs d’un système. Le parallel processing est une forme de concurrence (faire plusieurs choses en même temps).

Le code asynchrone est complètement différent. Le sharepoint code async est de ne pas utiliser le thread en cours pendant que l’opération est en cours. Le code async est généralement lié à des entrées / sorties ou basé sur des événements (comme une timer). Le code asynchrone est une autre forme de concurrence.

J’ai une intro async sur mon blog, ainsi qu’un article sur la façon dont async n’utilise pas de threads .

Notez que les tâches utilisées par la bibliothèque de tâches parallèle peuvent être planifiées sur des threads et exécuter du code. Les tâches utilisées par le modèle asynchrone basé sur des tâches n’ont pas de code et ne “s’exécutent” pas. Bien que les deux types de tâches soient représentés par le même type ( Task ), ils sont créés et utilisés de manière totalement différente. Je décris ces tâches de délégué et de promesse plus en détail sur mon blog.

En bref et très général – Non, ce ne sera généralement pas le cas. Mais cela nécessite peu de mots, car la “performance” peut être comprise de plusieurs manières.

Async / wait “gagne du temps” uniquement lorsque le “travail” est lié à l’entrée / la sortie. Toute application de celle-ci à des tâches liées au processeur introduira certains succès. En effet, si vous avez des calculs qui prennent 10 secondes sur votre (vos) processeur (s), append async / wait, c’est-à-dire: création de tâches, planification et synchronisation, appenda simplement un temps supplémentaire X à ces 10 secondes que vous devez encore graver. votre (vos) CPU (s) pour faire le travail. Quelque chose de proche de l’idée de la loi Amdahl. Pas vraiment ça, mais assez proche.

Cependant, il y a quelques ‘mais ..’ s.

Tout d’abord, les problèmes de performances dus à l’introduction async / wait ne sont pas très importants. (surtout si vous faites attention à ne pas en faire trop).

Deuxièmement, comme async / wait vous permet d’écrire beaucoup plus facilement le code entrelacé E / S, vous remarquerez peut-être de nouvelles possibilités de supprimer les temps d’attente sur les E / S dans des endroits où vous seriez trop paresseux (:) pour le faire autrement ou aux endroits où cela rendrait le code difficile à suivre sans bonté syntaxique async / wait. Par exemple, diviser le code en requêtes réseau est une chose assez évidente à faire, mais vous remarquerez peut-être que vous pouvez également mettre à niveau une entrée / sortie de fichier à quelques endroits où vous écrivez des fichiers CSV, lisez des fichiers de configuration, etc. que le gain ici ne sera pas dû à async / wait – ce sera grâce à la réécriture du code qui gère le fichier i / o. Vous pouvez le faire sans async / wait aussi.

Troisièmement, dans la mesure où certaines opérations d’E / S sont plus faciles, vous remarquerez peut-être qu’il est beaucoup plus facile de décharger le travail gourmand en ressources processeur vers un autre service ou une autre machine, ce qui peut également améliorer les performances que vous percevez (temps plus court). la consommation de ressources augmentera: ajout d’une autre machine, temps consacré aux opérations réseau, etc.

Quasortingème: l’interface utilisateur. Vous ne voulez vraiment pas le geler. Il est très facile de regrouper les tâches liées aux entrées / sorties et aux processeurs dans des tâches asynchrones / asynchrones et de conserver la réactivité de l’interface utilisateur. C’est pourquoi vous le voyez mentionné partout. Cependant, alors que les opérations liées aux E / S devraient idéalement être asynchrones jusqu’au bout pour laisser autant de temps d’attente en veille sur toutes les E / S longues, il n’est pas nécessaire que les tâches liées à la CPU soient scindées ou asyncisées. descendre d’un niveau. Avoir un travail de calcul monolithique énorme encapsulé dans une seule tâche suffit à débloquer l’interface utilisateur. Bien sûr, si vous avez plusieurs processeurs / cœurs, il est toujours intéressant de paralléliser tout ce qui est possible à l’intérieur, mais contrairement à l’E / S, divisez trop et vous serez occupé à changer de tâche au lieu de mâcher des calculs.

Résumer: si vous prenez du temps, les opérations d’asynchronisation peuvent vous faire gagner beaucoup de temps. Il est difficile d’exagérer les opérations d’E / S asynchronisées. Si vous avez des opérations prenant en charge le processeur, append quoi que ce soit demandera plus de temps de calcul et plus de mémoire, mais la durée de l’horloge murale peut être meilleure grâce au fractionnement du travail en plusieurs parties plus petites pouvant être exécutées simultanément sur plusieurs cœurs. temps. Il n’est pas difficile d’en faire trop, il faut donc être prudent.

Le plus souvent, vous ne gagnez pas en performance directe (la tâche que vous effectuez se produit plus rapidement et / ou avec moins de mémoire), contrairement à l’évolutivité; utiliser moins de threads pour effectuer le même nombre de tâches simultanées signifie que le nombre de tâches simultanées que vous pouvez effectuer est plus élevé.

Par conséquent, dans la plupart des cas, vous ne trouvez pas une opération donnée qui améliore les performances, mais vous pouvez constater qu’une utilisation intensive a amélioré les performances.

Si une opération nécessite des tâches parallèles impliquant quelque chose de vraiment asynchrone (plusieurs E / S asynchrones), cette évolutivité peut toutefois bénéficier à cette opération unique. Etant donné que le degré de blocage dans les threads est réduit, cela se produit même si vous ne possédez qu’un seul cœur, car la machine divise son temps uniquement entre les tâches qui ne sont pas en attente.

Cela diffère des opérations parallèles liées à la CPU qui (qu’elles soient effectuées à l’aide de tâches ou autrement) ne seront généralement mises à l’échelle que jusqu’au nombre de cœurs disponibles. (Les kernelx hyper-filetés se comportent comme 2 kernelx ou plus à certains égards et pas à d’autres).

La méthode s’exécute sur le contexte de synchronisation actuel et utilise l’heure sur le thread uniquement lorsque la méthode est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié à la CPU vers un fil d’arrière-plan, mais un fil d’arrière-plan ne vous aide pas dans un processus qui attend simplement que les résultats soient disponibles.

Lorsque vous avez un processeur et plusieurs threads dans votre application, votre processeur bascule entre les threads pour simuler un parallel processing. Avec async / wait, votre opération asynchrone n’a pas besoin de temps d’exécution. Vous donnez donc plus de temps aux autres threads de votre application pour effectuer le travail. Par exemple, votre application (non-UI) peut toujours passer des appels HTTP et il vous suffit d’attendre la réponse. C’est l’un des cas où l’avantage d’utiliser async / wait est énorme.

Lorsque vous appelez async DoJobAsync() n’oubliez pas de .ConfigureAwait(false) pour obtenir de meilleures performances pour les applications non-UI qui n’ont pas besoin de se fondre dans le contexte du thread UI.

Je ne mentionne pas la syntaxe agréable qui aide beaucoup à garder votre code propre.

MSDN

Les mots-clés async et wait n’entraînent pas la création de threads supplémentaires. Les méthodes asynchrones ne nécessitent pas de multithreading car une méthode asynchrone ne s’exécute pas sur son propre thread. La méthode s’exécute sur le contexte de synchronisation actuel et utilise l’heure sur le thread uniquement lorsque la méthode est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié à la CPU vers un fil d’arrière-plan, mais un fil d’arrière-plan ne vous aide pas dans un processus qui attend simplement que les résultats soient disponibles.

La fonctionnalité d’attente asynchrone de .NET n’est pas différente de celle d’autres frameworks. Cela ne donne pas un avantage en termes de performances, mais permet simplement une commutation continue entre les tâches d’un seul thread au lieu de laisser une tâche bloquer le thread. Si vous voulez un gain de performance, utilisez Task Parallel Library.

Visitez le site https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx.