[c#] Fire-and-Forget mit Async vs "alter Async-Delegierter"



Answers

Wie in den anderen Antworten und in diesem ausgezeichneten jaylee.org/post/2012/07/08/… , möchten Sie vermeiden, dass async void außerhalb von UI-Ereignishandlern verwendet werden. Wenn Sie eine sichere async Methode "feuern und vergessen" möchten, sollten Sie dieses Muster verwenden (Kredit an @ReedCopsey; diese Methode gibt er mir in einer Chat-Konversation):

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

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

  3. Rufen Sie diese Methoden auf diese Weise auf:

    MyTaskAsyncMethod().FireAndForget();
    

Sie müssen es nicht await (noch wird es die await erzeugen). Es wird auch alle Fehler korrekt behandeln , und da dies der einzige Ort ist, an dem Sie jemals async void , müssen Sie nicht daran denken, try/catch Blöcke überall zu platzieren.

Dies gibt Ihnen auch die Möglichkeit, die async Methode nicht als "Fire and Forget" -Methode zu verwenden, wenn Sie sie tatsächlich await möchten.

Question

Ich versuche, meine alten Feuer-und-Vergessen-Anrufe durch eine neue Syntax zu ersetzen, in der Hoffnung auf mehr Einfachheit und es scheint mir entgangen zu sein. 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 close handle, was imho noch ein wenig umstritten ist) Ich verliere auf neue Weise, indem ich auf eine Task.Yield() warten Task.Yield() stellt tatsächlich ein neues Problem dar, alle vorhandenen asynchronen F & F-Methoden neu schreiben zu müssen, nur um diesen Einliner hinzuzufügen. Gibt es einige unsichtbare Gewinne in Bezug auf die Leistung / Bereinigung?

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

Edit: Ich sehe jetzt, dass ich vielleicht eine echte Frage vermisse. Die Frage ist: Wie kann ich asynchrone Methode async / await auf eine Feuer-und-Vergessen-Weise aufrufen, ohne eine Lösung zu finden, die komplizierter ist als der "alte Weg"?




Mein Sinn ist, dass diese "Feuer und Vergessen" -Methoden größtenteils Artefakte waren, die einen sauberen Weg zum Verschachteln von UI- und Hintergrundcode benötigten, so dass Sie Ihre Logik immer noch als eine Folge sequenzieller Anweisungen schreiben können. Da async / await durch den SynchronizationContext für das Marshalling sorgt, wird dies weniger problematisch. Der Inline-Code in einer längeren Sequenz wird effektiv zu Ihren "Feuer und Vergessen" -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 eher Invoke als BeginInvoke ähneln. Wenn Sie ein ähnliches Verhalten wie BeginInvoke benötigen, können Sie die nächste asynchrone Methode aufrufen (indem Sie eine Aufgabe zurückgeben) und dann erst nach dem Code, den Sie 'BeginInvoke' verwenden wollten, auf die zurückgegebene Aufgabe warten.

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



Links