Je suis coincé sur cette question depuis un certain temps et je n'ai pas vraiment trouvé de clarification utile pour expliquer pourquoi.
Si j'ai une méthode async
comme:
public async Task<bool> MyMethod()
{
// Some logic
return true;
}
public async void MyMethod2()
{
var status = MyMethod(); // Visual studio green lines this and recommends using await
}
Si j'utilise await
ici, quel est l'intérêt de la méthode asynchrone? Cela ne rend-il pas inutile le async
que VS me dit d'appeler await
? Cela ne va-t-il pas à l'encontre du but de décharger une tâche sur un thread sans attendre sa fin?
Si j'utilise wait ici, quel est l'intérêt de la méthode asynchrone?
await
ne bloque pas le thread. MyMethod2
s'exécutera de manière synchrone jusqu'à ce qu'il atteigne l'expression await
. Alors MyMethod2
sera suspend jusqu'à ce que la tâche attendue (MyMethod
) soit terminée. Tant que MyMethod
n'est pas terminé, le contrôle revient à l'appelant de MyMethod2
. C'est le point de await
- l'appelant continuera à faire son travail.
Cela ne rend-il pas inutile l'async que VS me dit d'appeler attendre?
async
n'est qu'un indicateur qui signifie "quelque part dans la méthode, vous en avez un ou plusieurs en attente".
Cela ne va-t-il pas à l'encontre du but de décharger une tâche sur un thread sans attendre sa fin?
Comme décrit ci-dessus, vous n'avez pas à attendre la fin de la tâche. Rien n'est bloqué ici.
REMARQUE: pour suivre les normes de dénomination du framework, je vous suggère d'ajouter le suffixe Async
aux noms de méthodes asynchrones.
Cela ne va-t-il pas à l'encontre du but de décharger une tâche sur un thread sans attendre sa fin?
Oui bien sûr. Mais ce n'est pas pas le but de wait/async . Le but est de vous permettre d'écrire du code synchrone qui utilise des opérations asynchrones sans gaspiller les threads, ou plus généralement, pour donner à l'appelant une mesure de contrôle sur les opérations plus ou moins asynchrones.
L'idée de base est que tant que vous utilisez wait et async correctement, toute l'opération apparaîtra comme synchrone. C'est généralement une bonne chose, car la plupart des choses que vous faites sont synchrones - par exemple, vous ne voulez pas créer un utilisateur avant de demander le Nom d'utilisateur. Vous feriez donc quelque chose comme ceci:
var name = await GetNameAsync();
var user = await RemoteService.CreateUserAsync(name);
Les deux opérations sont synchrones l'une par rapport à l'autre; le second ne se produit pas (et ne peut pas!) se produire avant le premier. Mais ils ne sont pas (nécessairement) synchrones par rapport à leur appelant . Un exemple typique est une application Windows Forms. Imaginez que vous avez un bouton et que le gestionnaire de clics contient le code ci-dessus - tout le code s'exécute sur le thread d'interface utilisateur, mais en même temps, pendant que vous êtes await
ing, le thread d'interface utilisateur est libre de faire d'autres tâches (similaires à l'utilisation de Application.DoEvents
jusqu'à la fin de l'opération).
Le code synchrone est plus facile à écrire et à comprendre, ce qui vous permet d'obtenir la plupart des avantages des opérations asynchrones sans rendre votre code plus difficile à comprendre. Et vous ne perdez pas la capacité de faire les choses de manière asynchrone, car Task
lui-même n'est qu'une promesse, et vous ne le faites pas toujours doivent le await
immédiatement. Imaginez que GetNameAsync
prenne beaucoup de temps, mais en même temps, vous avez un peu de travail CPU à faire avant qu'il ne soit fait:
var nameTask = GetNameAsync();
for (int i = 0; i < 100; i++) Thread.Sleep(100); // Important busy-work!
var name = await nameTask;
var user = await RemoteService.CreateUserAsync(name);
Et maintenant, votre code est toujours magnifiquement synchrone - await
est le point de synchronisation - tandis que vous pouvez faire d'autres choses en parallèle avec les opérations asynchrones . Un autre exemple typique serait de lancer plusieurs demandes asynchrones en parallèle, mais en gardant le code synchrone avec l'achèvement de toutes les demandes:
var tasks = urls.Select(i => httpClient.GetAsync(i)).ToArray();
await Task.WhenAll(tasks);
Les tâches sont asynchrones les unes par rapport aux autres, mais pas à leur appelant, qui est toujours magnifiquement synchrone.
J'ai fait un (incomplet) exemple de mise en résea que les utilisations attendent de cette façon. L'idée de base est que, alors que la plupart du code est logiquement synchrone (il y a un protocole à suivre - demander la connexion-> vérifier la connexion-> lire la boucle ...; vous pouvez même voir la partie où plusieurs tâches sont attendues en parallèle) , vous n'utilisez un thread que lorsque vous avez réellement du travail CPU à faire. Attendre rend cela presque trivial - faire la même chose avec les continuations ou l'ancien modèle asynchrone Begin/End serait beaucoup plus douloureux, surtout en ce qui concerne la gestion des erreurs. Attendre le rend très propre.
Une méthode async n'est pas exécutée automatiquement sur un autre thread. En fait, l'inverse est vrai: une méthode async
est toujours exécutée dans le thread appelant. async
signifie que c'est une méthode qui peut céder à une opération asynchrone. Cela signifie qu'il peut retourner le contrôle à l'appelant en attendant la fin de l'autre exécution. Les méthodes asnync
sont donc un moyen d'attendre autre les opérations asynchrones.
Puisque vous ne faites rien à attendre dans MyMethod2
, async
n'a aucun sens ici, donc votre compilateur vous avertit.
Fait intéressant, l'équipe qui a implémenté les méthodes async
a reconnu que le marquage d'une méthode async
n'est pas vraiment nécessaire, car il suffirait d'utiliser simplement await
dans le corps de la méthode pour le compilateur pour le reconnaître comme async. L'obligation d'utiliser le mot clé async
a été ajoutée pour éviter d'interrompre les modifications du code existant qui utilise await
comme nom de variable.