Je pensais que c’était fondamentalement la même chose: écrire des programmes qui répartissaient les tâches entre les processeurs (sur les machines équipées de 2 processeurs ou plus). Puis je lis https://msdn.Microsoft.com/en-us/library/hh191443.aspx , qui dit
Les méthodes asynchrones sont conçues pour être des opérations non bloquantes. Une expression en attente dans une méthode asynchrone ne bloque pas le thread en cours pendant l'exécution de la tâche attendue. Au lieu de cela, l'expression inscrit le reste de la méthode en tant que continuation et redonne le contrôle à l'appelant de la méthode asynchrone.
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.
et je me demande si quelqu'un peut traduire cela en anglais pour moi. Il semble faire une distinction entre asyncronisme (est-ce un mot?) Et threading et implique que vous pouvez avoir un programme qui a des tâches asynchrones mais pas de multithreading.
Maintenant, je comprends l’idée de tâches asynchrones telles que l’exemple de la page. 467 de la troisième édition de Cs In Depth de Jon Skeet
async void DisplayWebsiteLength ( object sender, EventArgs e )
{
label.Text = "Fetching ...";
using ( HttpClient client = new HttpClient() )
{
Task<string> task = client.GetStringAsync("http://csharpindepth.com");
string text = await task;
label.Text = text.Length.ToString();
}
}
Le mot clé async
signifie ". Cette fonction, à chaque appel, ne sera pas appelée dans un contexte dans lequel elle doit être complétée pour que tout ce qui se passera après son appel soit appelé."
En d'autres termes, l'écrire au milieu d'une tâche
int x = 5;
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);
, _ étant donné que DisplayWebsiteLength()
n'a rien à voir avec x
ou y
, DisplayWebsiteLength()
sera exécuté "en arrière-plan", comme
processor 1 | processor 2
-------------------------------------------------------------------
int x = 5; | DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0); |
Évidemment, c'est un exemple stupide, mais ai-je raison ou suis-je totalement confus ou quoi?
(En outre, je ne comprends pas pourquoi sender
et e
ne sont jamais utilisés dans le corps de la fonction ci-dessus.)
Votre malentendu est extrêmement commun. Beaucoup de gens apprennent que le multithreading et l'asynchronisme sont la même chose, mais ce n'est pas le cas.
Une analogie aide généralement. Vous cuisinez dans un restaurant. Une commande vient pour des oeufs et des toasts.
Maintenant, est-il logique que le multithreading ne soit qu'un type d'asynchronisme? Le filetage concerne les travailleurs; l'asynchronie concerne les tâches . Dans les flux de travail multithreads, vous affectez des tâches à des travailleurs. Dans les workflows mono-threadés asynchrones, vous avez un graphique des tâches dans lesquelles certaines tâches dépendent des résultats d'autres tâches. à la fin de chaque tâche, elle appelle le code qui planifie la prochaine tâche pouvant être exécutée, en fonction des résultats de la tâche qui vient d'être terminée. Mais (espérons-le), vous n’avez besoin que d’un seul travailleur pour effectuer toutes les tâches, et non d’un travailleur par tâche.
Il sera utile de réaliser que de nombreuses tâches ne sont pas liées au processeur. Pour les tâches liées au processeur, il est judicieux d’embaucher autant de travailleurs (threads) qu’il ya de processeurs, d’attribuer une tâche à chaque travailleur, d’attribuer un processeur à chaque travailleur et de laisser chaque processeur s’acquitter de sa tâche autrement qu'en calculant le résultat. Aussi vite que possible. Mais pour les tâches qui n'attendent pas un processeur, vous n'avez pas du tout besoin d'affecter un ouvrier. Vous attendez simplement le message indiquant que le résultat est disponible et faites autre chose en attendant . Lorsque ce message arrive, vous pouvez planifier la poursuite de la tâche terminée en tant que tâche suivante de votre liste de tâches à cocher.
Alors regardons l'exemple de Jon plus en détail. Ce qui se produit?
text
et exécuter le reste de la méthode.C'est comme dans mon analogie. Quelqu'un vous demande un document. Vous envoyez le document par la poste et continuez d’autres travaux. Quand il arrive dans le courrier, vous êtes signalé et quand vous en avez envie, vous faites le reste du flux de travail - ouvrez l'enveloppe, payez les frais de livraison, peu importe. Vous n'avez pas besoin d'engager un autre travailleur pour faire tout cela pour vous.
Le navigateur Javascript est un bon exemple de programme asynchrone sans thread.
Vous n'avez pas à vous préoccuper du fait que plusieurs morceaux de code touchent les mêmes objets en même temps: chaque fonction s'achèvera avant que tout autre javascript soit autorisé à s'exécuter sur la page.
Toutefois, lorsqu’il s’agit d’une requête comme une requête AJAX, aucun code n’est en cours d’exécution, ce qui permet à d’autres javascript de réagir à des événements tels que les événements de clic jusqu’à ce que cette requête revienne et invoque le rappel qui lui est associé. Si l'un de ces autres gestionnaires d'événements est toujours en cours d'exécution lorsque la demande AJAX est récupérée, son gestionnaire ne sera pas appelé jusqu'à ce qu'ils aient terminé. Un seul "fil" JavaScript est en cours d'exécution, même s'il est possible de mettre en pause ce que vous faisiez jusqu'à ce que vous obteniez les informations dont vous avez besoin.
Dans les applications C #, la même chose se produit chaque fois que vous utilisez des éléments d'interface utilisateur: vous n'êtes autorisé à interagir avec des éléments d'interface utilisateur que si vous êtes sur le thread d'interface utilisateur. Si l'utilisateur clique sur un bouton et que vous souhaitez répondre en lisant un fichier volumineux sur le disque, un programmeur inexpérimenté peut commettre l'erreur de lire le fichier dans le gestionnaire d'événements click lui-même, ce qui entraînerait le "blocage" de l'application jusqu'au Le chargement du fichier est terminé car il n'est pas autorisé à répondre aux clics, survols ou autres événements liés à l'interface utilisateur tant que ce thread n'est pas libéré.
Une option que les programmeurs pourraient utiliser pour éviter ce problème est de créer un nouveau thread pour charger le fichier, puis d'indiquer à son code que, lorsque le fichier est chargé, il doit réexécuter le code restant sur le thread d'interface utilisateur pour pouvoir mettre à jour les éléments d'interface utilisateur. basé sur ce qu'il a trouvé dans le fichier. Jusqu'à récemment, cette approche était très populaire parce que c'était ce que les bibliothèques et le langage C # ont rendu facile, mais c'est fondamentalement plus compliqué que cela ne doit être.
Si vous pensez à ce que fait le processeur lorsqu'il lit un fichier au niveau du matériel/du système d'exploitation, il émet en gros une instruction permettant de lire des données du disque dans la mémoire et de frapper le système d'exploitation avec une "interruption" lorsque la lecture est terminée. En d’autres termes, lire sur le disque (ou n’importe quelle E/S) est une opération intrinsèquement asynchrone. Le concept d'un thread en attente d'exécution de cette E/S est une abstraction que les développeurs de bibliothèques ont créée pour faciliter la programmation. Ce n'est pas nécessaire.
Maintenant, la plupart des opérations d’E/S dans .NET ont une méthode correspondante que vous pouvez appeler, ...Async()
, qui renvoie presque immédiatement une Task
. Vous pouvez ajouter des rappels à cette Task
pour spécifier le code que vous souhaitez exécuter à la fin de l'opération asynchrone. Vous pouvez également spécifier le thread sur lequel vous souhaitez exécuter ce code et vous pouvez fournir un jeton que l'opération asynchrone peut vérifier de temps à autre pour voir si vous avez décidé d'annuler la tâche asynchrone, ce qui lui donne la possibilité d'arrêter rapidement son travail. et gracieusement.
Jusqu'à l'ajout des mots-clés async/await
, C # expliquait beaucoup plus clairement comment le code de rappel était appelé, car ces rappels se présentaient sous la forme de délégués que vous avez associés à la tâche. Afin de toujours vous faire bénéficier de l'utilisation de l'opération ...Async()
, tout en évitant la complexité du code, async/await
détourne la création de ces délégués. Mais ils sont toujours là dans le code compilé.
Ainsi, vous pouvez avoir votre gestionnaire d’événements UI await
une opération d’E/S, ce qui libère le thread d’UI et permet de revenir plus ou moins automatiquement au thread d’UI une fois la lecture du fichier terminée. -sans jamais avoir à créer un nouveau fil.