web-dev-qa-db-fra.com

async/wait - quand renvoyer une tâche vs vide?

Dans quels scénarios voudrait-on utiliser 

public async Task AsyncMethod(int num)

au lieu de 

public async void AsyncMethod(int num)

Le seul scénario auquel je peux penser est si vous avez besoin de la tâche pour pouvoir suivre ses progrès. 

En outre, dans la méthode suivante, les mots clés async et wait sont-ils inutiles?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}
373
user981225

1) Normalement, vous voudriez renvoyer une Task. La principale exception devrait être lorsque vous besoin d’avoir un type de retour void (pour les événements). S'il n'y a aucune raison de ne pas autoriser l'appelant à await votre tâche, pourquoi l'interdire?

2) Les méthodes async qui retournent void sont spéciales sous un autre aspect: elles représentent opérations asynchrones de niveau supérieur} et ont des règles supplémentaires qui entrent en jeu lorsque votre tâche renvoie une exception. Le moyen le plus simple est de montrer la différence, avec un exemple:

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

L'exception de f est toujours "observée". Une exception qui laisse une méthode asynchrone de niveau supérieur est simplement traitée comme toute autre exception non gérée. L'exception de g n'est jamais observée. Lorsque le ramasse-miettes vient nettoyer la tâche, il voit que celle-ci a généré une exception et que personne ne l'a traitée. Lorsque cela se produit, le gestionnaire TaskScheduler.UnobservedTaskException s'exécute. Vous ne devriez jamais laisser cela arriver. Pour utiliser votre exemple,

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

Oui, utilisez async et await ici, ils s’assurent que votre méthode fonctionne toujours correctement si une exception est levée.

pour plus d'informations, voir: http://msdn.Microsoft.com/en-us/magazine/jj991977.aspx

336
user743382

J'ai trouvé cet article très utile sur async et void écrit par Jérôme Laban: http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and- tricks-part-2-async-void.aspx

En fin de compte, un async+void peut planter le système et ne devrait généralement être utilisé que sur les gestionnaires d'événements latéraux de l'interface utilisateur.

La raison derrière cela est le contexte de synchronisation utilisé par le AsyncVoidMethodBuilder, étant nul dans cet exemple. Quand il n'y a pas ambient Synchronization Context, toute exception non gérée par le corps d'une méthode void asynchrone est redéfini sur le ThreadPool. Tandis que il n'y a apparemment aucun autre endroit logique où ce genre de non-traité exception pourrait être levée, le malheur est que le processus est en cours de fermeture, car des exceptions non gérées sur le ThreadPool terminer efficacement le processus depuis .NET 2.0. Vous pouvez intercepter toute exception non gérée à l'aide de l'événement AppDomain.UnhandledException, mais il n'y a aucun moyen de récupérer le processus à partir de cet événement.

Lors de l'écriture de gestionnaires d'événements d'interface utilisateur, les méthodes void async sont en quelque sorte indolore car les exceptions sont traitées de la même manière que dans méthodes non asynchrones; ils sont jetés sur le Dispatcher. Il y a un possibilité de récupérer de telles exceptions, avec est plus que correct pour la plupart des cas. En dehors des gestionnaires d'événements d'interface utilisateur, async void les méthodes sont en quelque sorte dangereuses à utiliser et peuvent ne pas être faciles à trouver.

30
Davide Icardi

J'ai eu une idée claire de ces déclarations.

  1. Les méthodes void async ont différentes sémantiques de traitement des erreurs. Lorsqu'une exception est rejetée à l'aide d'une tâche asynchrone ou d'une méthode de tâche asynchrone, cette exception est capturée et placée sur l'objet Task. Avec les méthodes void async, il n'y a pas d'objet Task, donc toutes les exceptions émises par une méthode void async seront levées directement sur le SynchronizationContext (SynchronizationContext représente un emplacement "où" du code pourrait être exécuté.) Qui était actif lorsque la méthode async void commencé

Les exceptions d'une méthode asynchrone Void ne peuvent pas être capturées

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

Ces exceptions peuvent être observées à l'aide de AppDomain.UnhandledException ou d'un événement fourre-tout similaire pour les applications GUI/ASP.NET, mais l'utilisation de ces événements pour la gestion des exceptions régulières est une recette pour la non-maintenabilité (elle bloque l'application).

  1. Les méthodes void async ont différentes sémantiques de composition. Les méthodes asynchrones renvoyant une tâche ou une tâche peuvent être facilement composées à l'aide de wait, Task.WhenAny, Task.WhenAll, etc. Les méthodes asynchrones renvoyant void ne fournissent pas un moyen facile de notifier le code d'appel qu'elles ont terminé. Il est facile de démarrer plusieurs méthodes void async, mais il n’est pas facile de déterminer quand elles sont terminées. Les méthodes void async notifient leur SynchronizationContext quand elles commencent et se terminent, mais un SynchronizationContext personnalisé est une solution complexe pour le code d'application normal.

  2. La méthode Async Void est utile lorsque vous utilisez un gestionnaire d'événements synchrone, car elles lèvent leurs exceptions directement sur le SynchronizationContext, ce qui est similaire au comportement des gestionnaires d'événements synchrones.

Pour plus de détails, consultez ce lien https://msdn.Microsoft.com/en-us/magazine/jj991977.aspx

21

Le problème lié à l'appel async void est que vous ne récupérez même pas la tâche. Vous n'avez aucun moyen de savoir quand la tâche de la fonction est terminée (voir https://blogs.msdn.Microsoft.com/oldnewthing/20170720- 00 /? P = 96655 )

Voici les trois manières d'appeler une fonction asynchrone:

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

Dans tous les cas, la fonction est transformée en une chaîne de tâches. La différence est ce que la fonction retourne. 

Dans le premier cas, la fonction retourne une tâche qui produit finalement le t. 

Dans le second cas, la fonction retourne une tâche qui n'a pas de produit, mais vous pouvez attend toujours sur elle pour savoir quand il est terminé. 

Le troisième cas est le méchant. Le troisième cas est comme le deuxième cas, sauf que vous ne récupérez même pas la tâche. Vous n'avez aucun moyen de savoir quand la tâche de la fonction est terminée. 

Le cas vide asynchrone est un "incendie et Oublier": vous démarrez la chaîne de tâches, mais vous ne vous souciez pas de savoir quand c'est fini. Lorsque la fonction revient, tout ce que vous savez, c'est que tout jusqu'à la première attente a été exécuté. Tout après la première attente se déroulera à un moment indéterminé dans le futur que vous n'avez pas accès à.

5
user8128167

Je pense que vous pouvez également utiliser async void pour lancer des opérations en arrière-plan, à condition de prendre les exceptions en compte. Pensées?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}
5
bboyle1234

Ma réponse est simplevous pouvez attendre la méthode void Erreur CS4008 Impossible d'attendre 'void' TestAsync e:\test\TestAsync\TestAsync\Program.cs 

Donc, si la méthode est asynchrone, il est préférable d’attendre, car vous pouvez perdre l’avantage async.

0
Serg Sh