Domanda Fire-and-forget con async vs "vecchio async delegato"


Sto cercando di sostituire le mie vecchie chiamate fire-and-forget con una nuova sintassi, sperando in una maggiore semplicità e sembra che mi stia sfuggendo. Ecco un esempio

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

Entrambi gli approcci fanno la stessa cosa, comunque quello che sembra aver guadagnato (non è necessario EndInvoke e maneggevolezza, che è ancora un po 'discutibile) Sto perdendo nel nuovo modo dovendo aspettare un Task.Yield(), che in realtà pone un nuovo problema di dover riscrivere tutti i metodi F & F asincroni esistenti solo per aggiungere quella singola. Ci sono guadagni invisibili in termini di prestazioni / pulizia?

Come faccio ad applicare async se non riesco a modificare il metodo di background? Mi sembra che non ci sia un modo diretto, dovrei creare un metodo async wrapper che attenda Task.Run ()?

Edit: Ora vedo che potrei mancare una vera domanda. La domanda è: Dato un metodo sincrono A (), come posso chiamarlo usando in modo asincrono async/await in modo fuoco-e-dimentica senza ottenere una soluzione che è più complicata della "vecchia maniera"


44
2017-10-09 15:04


origine


risposte:


Evitare async void. Ha semantica ingannevole sulla gestione degli errori; So che alcune persone lo chiamano "fire and forget", ma di solito uso la frase "fire and crash".

La domanda è: Dato un metodo sincrono A (), come posso chiamarlo in modo asincrono usando async / attendi in modo fire-and-forget senza ottenere una soluzione che è più complicata della "vecchia maniera"

Non hai bisogno async / await. Chiamalo in questo modo:

Task.Run(A);

62
2017-10-09 16:06



Come notato nelle altre risposte, e da questo eccellente post sul blog vuoi evitare di usarlo async void al di fuori dei gestori di eventi UI. Se vuoi un sicuro "fuoco e dimentica" async metodo, considera l'utilizzo di questo modello (credito a @ReedCopsey; questo metodo è uno che mi ha dato in una conversazione chat):

  1. Creare un metodo di estensione per Task. Corre il passato Task e cattura / registra eventuali eccezioni:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. Usa sempre Task stile async metodi durante la loro creazione, mai async void.

  3. Invoca questi metodi in questo modo:

    MyTaskAsyncMethod().FireAndForget();
    

Non è necessario await esso (né genererà il await avvertimento). Gestirà anche eventuali errori correttamentee siccome questo è l'unico posto che tu abbia mai messo async void, non devi ricordarti di mettere try/catch blocchi ovunque.

Questo ti dà anche la possibilità di non usando il async metodo come metodo "fuoco e dimentica" se in realtà lo vuoi await normalmente


30
2018-01-09 01:19



A me sembra che "aspettando" qualcosa e "accendi e dimentichi" siano due concetti ortogonali. Si avvia un metodo in modo asincrono e non si cura del risultato, oppure si desidera riprendere l'esecuzione nel contesto originale al termine dell'operazione (e possibilmente utilizzare un valore restituito), che è esattamente ciò che attende. Se si desidera eseguire un metodo su un thread ThreadPool (in modo che l'interfaccia utente non venga bloccata), andare avanti

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

e starai bene.


15
2017-10-09 15:26



La mia sensazione è che questi metodi "fire and forget" fossero in gran parte artefatti di aver bisogno di un modo pulito per interlacciare l'interfaccia utente e il codice di background in modo da poter scrivere la tua logica come una serie di istruzioni sequenziali. Poiché async / await si occupa del marshalling attraverso SynchronizationContext, questo diventa meno di un problema. Il codice inline in una sequenza più lunga diventa effettivamente i blocchi di "fire and forget" che in precedenza sarebbero stati lanciati da una routine in un thread in background. È effettivamente un'inversione del modello.

La differenza principale è che i blocchi tra le attese sono più simili a Invoke di BeginInvoke. Se hai bisogno di un comportamento più simile a BeginInvoke, puoi chiamare il prossimo metodo asincrono (restituendo un'attività), quindi non attendere l'attività restituita fino a dopo il codice che desideri "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;
    }

0
2017-10-09 15:16