J'étais en train de voir comment utiliser async attendre, mais je ne comprends pas très bien quand nous avons plusieurs méthodes qui s'appellent les unes les autres. Devrions-nous toujours utiliser attendre ou devrions-nous utiliser attendre uniquement lorsque nous sommes réellement prêts à utiliser le résultat?
Donc, par exemple, devrions-nous le faire comme ceci:
async Task<string[]> FooAsync()
{
var info = await Func1();
return info.split('.');
}
async Task<string> Func1()
{
return await Func2();
}
async Task<string> Func2()
{
return await tcpClient.ReadStringAsync();
}
Ou comme ça:
async Task<string[]> FooAsync()
{
var info = await Func1();
return info.split('.');
}
Task<string> Func1()
{
return Func2();
}
Task<string> Func2()
{
return tcpClient.ReadStringAsync();
}
Par l'exemple 1, devrions-nous toujours utiliser l'attente dans chaque méthode?
Ou
Par l'exemple 2, devrions-nous uniquement utiliser wait sur la méthode la plus élevée lorsque nous commençons à utiliser le résultat?
Chaque fois que vous appelez await
, il crée un bloc de code pour regrouper variables, capture le contexte synchronisation (le cas échéant) et crée une continuation dans un IAsyncStateMachine
.
Essentiellement, renvoyer un Task
sans le async
mot-clé vous donnera une petite exécution efficacité et vous fera économiser un tas de [~ # ~] cil [~ # ~]. Notez que la fonctionnalité Async de . NET possède également de nombreuses optimisations. Notez également (et surtout) que le renvoi d'un Task
dans une instruction using
générera probablement une exception déjà supprimée.
Vous pouvez comparer le [~ # ~] cil [~ # ~] et les différences de plomberie ici
Donc, si votre méthode ne fait que transférer un Task
et n'en veut rien, vous pouvez facilement supprimer le mot clé async
et renvoyer directement le Task
.
De plus, il y a des moments où nous faisons plus que simplement transfert et il y a des branchements impliqués. C'est ici que, Task.FromResult
et Task.CompletedTask
entrer en jeu pour aider à gérer la logique de ce qui peut survenir dans une méthode. C'est-à-dire si vous voulez donner un résultat (ici et là), ou retour un Task
c'est-à-dire terminé (respectivement).
Enfin, le Async and Await Pattern a des différences subtiles lorsqu'il s'agit de Exceptions. Si vous renvoyez un Task
, vous pouvez utiliser Task.FromException<T>
pour faire apparaître toute exception sur le Task
renvoyé comme le ferait normalement une méthode async
.
Exemple absurde
public Task<int> DoSomethingAsync(int someValue)
{
try
{
if (someValue == 1)
return Task.FromResult(3); // Return a completed task
return MyAsyncMethod(); // Return a task
}
catch (Exception e)
{
return Task.FromException<int>(e); // Place exception on the task
}
}
En bref, si vous ne comprenez pas très bien ce qui se passe, juste await
it; les frais généraux seront minimes. Cependant, si vous comprenez les sous-titres sur la façon de renvoyer un résultat de la tâche, un la tâche terminée, en plaçant une exception sur une tâche, ou tout simplement transfert. Vous pouvez vous sauver un peu [~ # ~] cil [~ # ~] et donner à votre code un petit gain de performances en supprimant le mot clé async
renvoyant une tâche directement et en contournant le IAsyncStateMachine
.
À peu près à cette époque, je cherchais l'utilisateur et l'auteur de Stack Overflow Stephen Cleary, et M. Parallel Stephen Toub. Ils ont une pléthore de blogs et de livres dédiés uniquement au Async and Await Pattern, tous les pièges, le codage de l'étiquette et beaucoup d'autres informations que vous trouverez sûrement intéressantes.
Les deux options sont légitimes et chaque option a ses propres scénarios où elle est plus efficace qu'une autre.
Bien sûr, utilisez toujours wait lorsque vous souhaitez gérer le résultat de la méthode asynchrone ou gérer une éventuelle exception dans la méthode actuelle.
public async Task Execute()
{
try
{
await RunAsync();
}
catch (Exception ex)
{
// Handle thrown exception
}
}
Si vous n'utilisez pas le résultat de la méthode asynchrone dans la méthode actuelle - renvoyez la tâche. Cette approche retardera la création de la machine d'état pour l'appelant ou là où une tâche finale sera attendue. Comme indiqué dans les commentaires, l'exécution peut être un peu plus efficace.
Mais il y a des scénarios où vous devez attendre la tâche, même si vous ne faites rien avec le résultat et que vous ne voulez pas gérer les exceptions possibles
public Task<Entity> GetEntity(int id)
{
using (var context = _contextFactory.Create())
{
return context.Entities.FindAsync(id);
}
}
Dans le scénario ci-dessus, FindAsync
peut renvoyer une tâche non terminée et cette tâche sera retournée immédiatement à l'appelant et supprimera l'objet context
créé dans l'instruction using
.
Plus tard, lorsque l'appelant "attendra", l'exception de tâche sera levée car il essaiera d'utiliser un objet déjà supprimé (context
).
public async Task<Entity> GetEntity(int id)
{
using (var context = _contextFactory.Create())
{
return await context.Entities.FindAsync(id);
}
}
Et les réponses traditionnelles sur Async Await doivent inclure un lien vers le blog de Stephen Cleary
Eliding Async et Await
Await est une fonctionnalité de séquençage qui permet à l'appelant de recevoir le résultat d'une méthode asynchrone et de faire quelque chose avec. Si vous n'avez pas besoin de traiter le résultat d'une fonction asynchrone, vous n'avez pas à l'attendre.
Dans votre exemple, Func1()
et Func2()
ne traitent pas les valeurs de retour des fonctions asynchrones appelées, il est donc normal de ne pas les attendre.
Lorsque vous utilisez attendre, le code attend la fin de la fonction asynchrone. Cela devrait être fait lorsque vous avez besoin d'une valeur à partir d'une fonction asynchrone, comme dans ce cas:
int salary = await CalculateSalary();
...
async Task<int> CalculateSalary()
{
//Start high cpu usage task
...
//End high cpu usage task
return salary;
}
Si vous n'aviez pas utilisé l'attente, cela se produirait:
int salary = CalculateSalary().Result;
...
async Task<int> CalculateSalary()
{
//Start high cpu usage task
... //In some line of code the function finishes returning null because we didn't wait the function to finish
return salary; //This never runs
}
Attendre signifie, attendez que cette fonction asynchrone se termine.
Utilisez-le selon vos besoins, vos cas 1 et 2 produiraient le même résultat, tant que vous attendez lorsque vous attribuez la valeur info, le code sera en sécurité.
Source: https://docs.Microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index
Je crois que le 2ème fera l'affaire car attendre attend une valeur de retour. Comme il attend que la fonction Func1()
renvoie une valeur, Func1()
exécute déjà Func2()
qui renvoie une valeur.