En fait, asynchrone / attendre n'est pas si magique. Le sujet complet est assez large mais pour une réponse rapide mais suffisamment complète à votre question, je pense que nous pouvons gérer.
Abordons un simple événement de clic de bouton dans une application Windows Forms:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
Je vais explicitement ne pas parler de tout ce qu'il GetSomethingAsync
retourne pour l'instant. Disons simplement que c'est quelque chose qui se terminera après, disons, 2 secondes.
Dans un monde traditionnel, non asynchrone, votre gestionnaire d'événements de clic de bouton ressemblerait à ceci:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
Lorsque vous cliquez sur le bouton du formulaire, l'application semble se bloquer pendant environ 2 secondes, pendant que nous attendons que cette méthode soit terminée. Ce qui se passe, c'est que la "pompe à messages", essentiellement une boucle, est bloquée.
Cette boucle demande continuellement aux fenêtres "Quelqu'un a-t-il fait quelque chose, comme avoir déplacé la souris, cliqué sur quelque chose? Dois-je repeindre quelque chose? Si oui, dites-le moi!" puis traite ce "quelque chose". Cette boucle a reçu un message indiquant que l'utilisateur a cliqué sur "button1" (ou le type de message équivalent de Windows) et a fini par appeler notre button1_Click
méthode ci-dessus. Jusqu'à ce que cette méthode revienne, cette boucle est maintenant bloquée en attente. Cela prend 2 secondes et pendant ce temps, aucun message n'est en cours de traitement.
La plupart des choses qui traitent des fenêtres se font à l'aide de messages, ce qui signifie que si la boucle de messages arrête de pomper des messages, même pendant une seconde, elle est rapidement visible par l'utilisateur. Par exemple, si vous déplacez le bloc-notes ou tout autre programme au-dessus de votre propre programme, puis de nouveau à l'écart, une rafale de messages de peinture est envoyée à votre programme indiquant quelle région de la fenêtre qui est maintenant soudainement redevenue visible. Si la boucle de messages qui traite ces messages attend quelque chose, bloquée, aucune peinture n'est effectuée.
Donc, si dans le premier exemple, async/await
ne crée pas de nouveaux threads, comment le fait-il?
Eh bien, ce qui se passe, c'est que votre méthode est divisée en deux. C'est l'un de ces types de sujets généraux, donc je n'entrerai pas dans trop de détails, mais il suffit de dire que la méthode est divisée en ces deux choses:
- Tout le code menant à
await
, y compris l'appel àGetSomethingAsync
- Tout le code suivant
await
Illustration:
code... code... code... await X(); ... code... code... code...
Réorganisé:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Fondamentalement, la méthode s'exécute comme ceci:
- Il exécute tout jusqu'à
await
Il appelle la GetSomethingAsync
méthode, qui fait son travail, et renvoie quelque chose qui se terminera 2 secondes à l'avenir
Jusqu'à présent, nous sommes toujours dans l'appel d'origine à button1_Click, qui se produit sur le thread principal, appelé à partir de la boucle de message. Si le code menant à await
prend beaucoup de temps, l'interface utilisateur se figera toujours. Dans notre exemple, pas tellement
Ce que le await
mot - clé, avec une magie de compilateur intelligent, fait, c'est essentiellement quelque chose comme "Ok, vous savez quoi, je vais simplement revenir du gestionnaire d'événements de clic de bouton ici. Lorsque vous (comme dans, la chose que nous" re en attente de) terminer, faites le moi savoir car il me reste du code à exécuter ".
En fait, il indiquera à la classe SynchronizationContext que c'est fait, ce qui, selon le contexte de synchronisation réel en cours en ce moment, sera mis en file d'attente pour exécution. La classe de contexte utilisée dans un programme Windows Forms la mettra en file d'attente à l'aide de la file d'attente que la boucle de messages pompe.
Il revient donc à la boucle des messages, qui est maintenant libre de continuer à pomper les messages, comme déplacer la fenêtre, la redimensionner ou cliquer sur d'autres boutons.
Pour l'utilisateur, l'interface utilisateur est à nouveau réactive, traitant les autres clics sur les boutons, redimensionnant et, plus important encore, redessinant , de sorte qu'elle ne semble pas se figer.
- 2 secondes plus tard, la chose que nous attendons se termine et ce qui se passe maintenant est qu'il (enfin, le contexte de synchronisation) place un message dans la file d'attente que la boucle de message regarde, en disant "Hé, j'ai encore du code pour vous d'exécuter ", et ce code est tout le code après l'attente.
- Lorsque la boucle de message parvient à ce message, elle "retapera" essentiellement cette méthode là où elle s'était arrêtée, juste après
await
et continuera à exécuter le reste de la méthode. Notez que ce code est à nouveau appelé à partir de la boucle de message, donc si ce code arrive à faire quelque chose de long sans l'utiliser async/await
correctement, il bloquera à nouveau la boucle de message
Il y a beaucoup de pièces mobiles sous le capot ici, donc voici quelques liens vers plus d'informations, j'allais dire "si vous en avez besoin", mais ce sujet est assez large et il est assez important de connaître certaines de ces pièces mobiles . Invariablement, vous allez comprendre que l'async / wait est toujours un concept qui fuit. Certaines limitations et problèmes sous-jacents continuent de fuir dans le code environnant, et s'ils ne le font pas, vous finissez généralement par devoir déboguer une application qui se casse au hasard pour apparemment aucune bonne raison.
OK, et si GetSomethingAsync
tourne un fil qui se terminera en 2 secondes? Oui, alors évidemment, il y a un nouveau fil en jeu. Ce thread, cependant, n'est pas à cause de l'async-ness de cette méthode, c'est parce que le programmeur de cette méthode a choisi un thread pour implémenter du code asynchrone. Presque toutes les E / S asynchrones n'utilisent pas de thread, elles utilisent différentes choses. async/await
par eux - mêmes ne font pas tourner de nouveaux threads mais évidemment les "choses que nous attendons" peuvent être implémentées en utilisant des threads.
Il y a beaucoup de choses dans .NET qui ne font pas nécessairement tourner un thread de leur propre chef mais sont toujours asynchrones:
- Demandes Web (et bien d'autres choses liées au réseau qui prennent du temps)
- Lecture et écriture de fichiers asynchrones
- et bien d'autres, un bon signe est que la classe / interface en question a des méthodes nommées
SomethingSomethingAsync
ou BeginSomething
et EndSomething
et qu'il y en ait une IAsyncResult
.
Habituellement, ces choses n'utilisent pas de fil sous le capot.
OK, donc vous voulez une partie de ce "sujet large"?
Eh bien, demandons à Try Roslyn notre clic sur le bouton:
Essayez Roslyn
Je ne vais pas créer de lien dans la classe générée ici, mais c'est un truc assez sanglant.