webentwicklung-frage-antwort-db.com.de

Fire-and-Forget mit Async gegen "alten Async-Delegierten"

Ich versuche, meine alten Fire-and-Forget-Aufrufe durch eine neue Syntax zu ersetzen, in der Hoffnung auf mehr Einfachheit und es scheint mir zu entgehen. Hier ist ein Beispiel

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();
    }
}

Beide Ansätze machen das Gleiche, aber was ich gewonnen zu haben scheint (keine Notwendigkeit, EndInvoke und den Griff zu schließen, was imho noch ein bisschen umstritten ist), verliere ich auf die neue Art und Weise, indem ich auf eine Task.Yield() warten muss. Dies wirft tatsächlich das Problem auf, alle vorhandenen asynchronen F & F-Methoden neu schreiben zu müssen, um nur diesen Einzeiler hinzuzufügen. Gibt es einige unsichtbare Verbesserungen in Bezug auf Leistung/Bereinigung?

Wie wende ich Async an, wenn ich die Hintergrundmethode nicht ändern kann? Scheint mir, dass es keinen direkten Weg gibt, ich müsste eine asynchrone Wrapper-Methode erstellen, die auf Task.Run () warten würde?

Edit: Ich sehe jetzt, dass mir echte Fragen fehlen könnten. Die Frage ist: Wenn eine synchrone Methode A () gegeben ist, wie kann ich sie mit async/await asynchron aufrufen, ohne eine Lösung zu erhalten, die komplizierter ist als die "Alter Weg"

58
mmix

Vermeiden Sie async void. Es hat eine knifflige Semantik in Bezug auf die Fehlerbehandlung. Ich weiß, dass manche Leute es "Feuer und Vergessen" nennen, aber ich verwende normalerweise den Ausdruck "Feuer und Absturz".

Die Frage ist: Wenn eine synchrone Methode A () gegeben ist, wie kann ich sie unter Verwendung von async/await asynchron aufrufen, ohne eine Lösung zu erhalten, die komplizierter ist als der "alte Weg".

Sie brauchen async/await nicht. Nennen Sie es einfach so:

Task.Run(A);
82
Stephen Cleary

Wie in den anderen Antworten vermerkt, und durch diese ausgezeichnete Blog-Post möchten Sie vermeiden, async void außerhalb von UI-Ereignishandlern. Wenn Sie eine safe "fire and forget" async -Methode wünschen, ziehen Sie die Verwendung dieses Musters in Betracht (Dank an @ReedCopsey; diese Methode hat er mir in einem Chat-Gespräch gegeben). :

  1. Erstellen Sie eine Erweiterungsmethode für Task. Es führt das übergebene Task aus und fängt/protokolliert alle Ausnahmen:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. Verwenden Sie beim Erstellen immer Task Stil async Methoden, niemals async void.

  3. Rufen Sie diese Methoden folgendermaßen auf:

    MyTaskAsyncMethod().FireAndForget();
    

Sie müssen es nicht await (noch wird die Warnung await generiert). Es wird auch alle Fehler behandeln richtig, und da dies der einzige Ort ist, den Sie jemals setzen async void, du musst nicht daran denken, try/catch blockiert überall.

Dies gibt Ihnen auch die Möglichkeit, nicht die async -Methode als "fire and forget" -Methode zu verwenden, wenn Sie sie normalerweise await möchten.

49
BradleyDotNET

Mir scheint, dass das "Warten" auf etwas und das "Feuern und Vergessen" zwei orthogonale Konzepte sind. Entweder starten Sie eine Methode asynchron und kümmern sich nicht um das Ergebnis, oder Sie möchten die Ausführung im ursprünglichen Kontext fortsetzen, nachdem die Operation abgeschlossen wurde (und möglicherweise einen Rückgabewert verwenden). Genau das erwartet Sie. Wenn Sie nur eine Methode für einen ThreadPool-Thread ausführen möchten (damit Ihre Benutzeroberfläche nicht blockiert wird), gehen Sie zu

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

und dir wird es gut gehen.

18
Daniel C. Weber

Ich habe das Gefühl, dass diese "Feuer und Vergessen" -Methoden größtenteils Artefakte waren, die eine saubere Methode zur Verschachtelung von Benutzeroberfläche und Hintergrundcode erforderten, damit Sie Ihre Logik weiterhin als eine Reihe von sequentiellen Anweisungen schreiben können. Da async/await das Marshalling über den SynchronizationContext übernimmt, ist dies kein Problem mehr. Der Inline-Code in einer längeren Sequenz wird effektiv zu Ihren 'fire and forget'-Blöcken, die zuvor von einer Routine in einem Hintergrund-Thread gestartet wurden. Es ist effektiv eine Umkehrung des Musters.

Der Hauptunterschied besteht darin, dass die Blöcke zwischen den Wartezeiten Invoke ähnlicher sind als BeginInvoke. Wenn Sie ein Verhalten wie BeginInvoke benötigen, können Sie die nächste asynchrone Methode aufrufen (eine Aufgabe zurückgeben) und dann die zurückgegebene Aufgabe erst nach dem Code abwarten, den Sie 'BeginInvoke' möchten.

    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