web-dev-qa-db-fra.com

Tirez et oubliez avec async vs "ancien délégué async"

J'essaie de remplacer mes anciens appels de feu et d'oublier par une nouvelle syntaxe, en espérant plus de simplicité et cela semble m'échapper. Voici un exemple

class Program
{
    static void DoIt(string entry) 
    { 
        Console.WriteLine("Message: " + entry);
    }

    static async void DoIt2(string entry)
    {
        await Task.Yield();
        Console.WriteLine("Message2: " + entry);
    }

    static void Main(string[] args)
    {
        // old way
        Action<string> async = DoIt;
        async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
        Console.WriteLine("old-way main thread invoker finished");
        // new way
        DoIt2("Test2");   
        Console.WriteLine("new-way main thread invoker finished");
        Console.ReadLine();
    }
}

Les deux approches font la même chose, mais ce que je semble avoir gagné (pas besoin de EndInvoke et de fermer la poignée, ce qui est encore un peu discutable à mon humble avis) Je perds de la nouvelle manière en devant attendre un Task.Yield(), ce qui pose en fait un nouveau problème de devoir réécrire toutes les méthodes asynchrones F&F existantes juste pour ajouter cette ligne unique. Y a-t-il des gains invisibles en termes de performances/nettoyage?

Comment pourrais-je appliquer l'application asynchrone si je ne peux pas modifier la méthode d'arrière-plan? Il me semble qu'il n'y a pas de moyen direct, je devrais créer une méthode asynchrone wrapper qui attendrait Task.Run ()?

Edit: Je vois maintenant que je manque peut-être de vraies questions. La question est la suivante: étant donné une méthode synchrone A (), comment puis-je l'appeler de manière asynchrone en utilisant async/await d'une manière qui tire et oublie sans obtenir une solution plus compliquée que la "à l'ancienne"

58
mmix

Éviter async void. Il a une sémantique délicate sur la gestion des erreurs; Je sais que certaines personnes l'appellent "feu et oublie" mais j'utilise habituellement l'expression "feu et crash".

La question est la suivante: étant donné une méthode synchrone A (), comment puis-je l'appeler de manière asynchrone en utilisant asynchrone/attendre de manière à tirer et oublier sans obtenir une solution plus compliquée que "l'ancienne"

Vous n'avez pas besoin de async/await. Appelez-le comme ceci:

Task.Run(A);
82
Stephen Cleary

Comme indiqué dans les autres réponses, et par cet excellent article de blog vous voulez éviter d'utiliser async void en dehors des gestionnaires d'événements de l'interface utilisateur. Si vous voulez une méthode sûre "tirer et oublier" async, pensez à utiliser ce modèle (crédit à @ReedCopsey; cette méthode en est une il m'a donné lors d'une conversation):

  1. Créez une méthode d'extension pour Task. Il exécute le Task passé et intercepte/enregistre toutes les exceptions:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. Utilisez toujours les méthodes Task style async lors de leur création, jamais async void.

  3. Appelez ces méthodes de cette façon:

    MyTaskAsyncMethod().FireAndForget();
    

Vous n'avez pas besoin de le await (ni de générer l'avertissement await). Il gérera également toutes les erreurs correctement , et comme c'est le seul endroit où vous avez mis async void, vous n'avez pas à vous rappeler de mettre try/catch bloque partout.

Cela vous donne également la possibilité de ne pas utiliser la méthode async comme méthode "feu et oublier" si vous voulez réellement await normalement.

49
BradleyDotNET

Il me semble que "attendre" quelque chose et "tirer et oublier" sont deux concepts orthogonaux. Soit vous démarrez une méthode de manière asynchrone et ne vous souciez pas du résultat, soit vous souhaitez reprendre l'exécution sur le contexte d'origine une fois l'opération terminée (et éventuellement utiliser une valeur de retour), ce qui est exactement ce qui attend. Si vous souhaitez simplement exécuter une méthode sur un thread ThreadPool (afin que votre interface utilisateur ne soit pas bloquée), optez pour

Task.Factory.StartNew(() => DoIt2("Test2"))

et tout ira bien.

18
Daniel C. Weber

Mon sentiment est que ces méthodes `` tirer et oublier '' étaient en grande partie des artefacts nécessitant un moyen propre d'entrelacer l'interface utilisateur et le code d'arrière-plan afin que vous puissiez toujours écrire votre logique sous la forme d'une série d'instructions séquentielles. Comme async/wait s'occupe du marshaling via le SynchronizationContext, cela devient moins un problème. Le code en ligne dans une séquence plus longue devient effectivement vos blocs `` feu et oubliez '' qui auraient précédemment été lancés à partir d'une routine dans un thread d'arrière-plan. C'est en fait une inversion du motif.

La principale différence est que les blocs entre les attentes s'apparentent plus à Invoke qu'à BeginInvoke. Si vous avez besoin d'un comportement plus semblable à BeginInvoke, vous pouvez appeler la méthode asynchrone suivante (renvoyer une tâche), puis n'attendez pas la tâche retournée avant le code que vous vouliez 'BeginInvoke'.

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }
1
Dan Bryant