web-dev-qa-db-fra.com

Comment utiliser async attendre?

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?

40
Vincent

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.

28
Michael Randall

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

13
Fabio

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.

1
Phillip Ngan

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.

0