Soppressione "avviso CS4014: poiché questa chiamata non è attesa, l'esecuzione del metodo corrente continua ..."


156

Questo non è un duplicato di "Come chiamare in modo sicuro un metodo asincrono in C # senza attendere" .

Come sopprimo bene il seguente avviso?

avviso CS4014: poiché questa chiamata non è attesa, l'esecuzione del metodo corrente continua prima del completamento della chiamata. Valuta la possibilità di applicare l'operatore "wait" al risultato della chiamata.

Un semplice esempio:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Cosa ho provato e non mi è piaciuto:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Aggiornato , poiché la risposta originale accettata è stata modificata, ho modificato la risposta accettata a quella usando gli scarti di C # 7.0 , poiché non credo ContinueWithsia appropriato qui. Ogni volta che ho bisogno di registrare le eccezioni per le operazioni antincendio e dimentico, uso qui un approccio più elaborato proposto da Stephen Cleary .


1
Quindi, pensi che #pragmanon sia carino?
— Frédéric Hamidi,

10
@ FrédéricHamidi, lo faccio.
— noseratio,

2
@Noseratio: Ah, giusto. Scusa, pensavo fosse l'altro avvertimento. Ignorarmi!
— Jon Skeet,

3
@Terribad: Non ne sono davvero sicuro - sembra che l'avviso sia abbastanza ragionevole nella maggior parte dei casi. In particolare, dovresti pensare a ciò che vuoi che accada a eventuali guasti - di solito anche per "sparare e dimenticare" dovresti capire come registrare i guasti, ecc.
— Jon Skeet,

4
@Terribad, prima di usarlo in questo modo o in un altro, dovresti avere un quadro chiaro di come vengono propagate le eccezioni per i metodi asincroni (controlla questo ). Quindi, la risposta di @ Knaģis fornisce un modo elegante di non perdere alcuna eccezione per il fuoco-e-dimenticare, tramite un async voidmetodo di supporto.
— noseratio,

Risposte:


160

Con C # 7 ora puoi usare gli scarti :

_ = WorkAsync();

7
Questa è una funzione di lingua poco utile che non riesco proprio a ricordare. È come se ci fosse un _ = ...nel mio cervello.
— Marc L.

3
Ho trovato un SupressMessage rimosso il mio avviso dal mio "Elenco errori" di Visual Studio ma non "Output" e #pragma warning disable CSxxxxsembra più brutto dello scarto;)
— David Savage

122

È possibile creare un metodo di estensione che impedirà l'avviso. Il metodo di estensione può essere vuoto o è possibile aggiungere la gestione delle eccezioni con .ContinueWith()lì.

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Tuttavia ASP.NET conta il numero di attività in esecuzione, quindi non funzionerà con l' Forget()estensione semplice come elencato sopra e invece potrebbe non riuscire con l'eccezione:

Un modulo o gestore asincrono completato mentre un'operazione asincrona era ancora in sospeso.

Con .NET 4.5.2 può essere risolto usando HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}

8
Ho trovato TplExtensions.Forget. C'è molta più bontà sotto Microsoft.VisualStudio.Threading. Vorrei che fosse reso disponibile per l'uso al di fuori di Visual Studio SDK.
— noseratio,

1
@Noseratio e Knagis, mi piace questo approccio e ho intenzione di usarlo. Ho pubblicato una domanda di follow-up correlata: stackoverflow.com/questions/22864367/fire-and-forget-approach
— Matt Smith

3
@stricq Quale scopo servirebbe per aggiungere ConfigureAwait (false) a Forget ()? A quanto ho capito, ConfigureAwait influisce solo sulla sincronizzazione dei thread nel punto in cui waitit viene utilizzato su un'attività, ma lo scopo di Forget () è di eliminare l'attività, quindi l'attività non può mai essere attesa, quindi ConfigureAwait qui è inutile.
— dthorpe,

3
Se il thread di spawn scompare prima dell'incendio e dimentica che l'attività è completa, senza ConfigureAwait (false) proverà comunque a ricominciare a eseguire il marshalling sul thread di spawn, quel thread scompare e si blocca. L'impostazione di ConfigureAwait (false) indica al sistema di non eseguire il marshalling sul thread chiamante.
— Stricq,

2
Questa risposta ha una modifica per gestire un caso specifico, oltre a una dozzina di commenti. Semplicemente le cose sono spesso le cose giuste, scegli gli scarti! E cito la risposta di @ fjch1997: è stupido creare un metodo che richiede qualche altro
— Teejay,

39

Puoi decorare il metodo con il seguente attributo:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Fondamentalmente stai dicendo al compilatore che sai cosa stai facendo e non ha bisogno di preoccuparsi di possibili errori.

La parte importante di questo codice è il secondo parametro. La parte "CS4014:" è ciò che sopprime l'avviso. Puoi scrivere tutto quello che vuoi sul resto.


Per me non funziona: Visual Studio per Mac 7.0.1 (build 24). Sembra che dovrebbe ma - no.
— IronRod

1
[SuppressMessage("Compiler", "CS4014")]sopprime il messaggio nella finestra Elenco errori, ma la finestra Output mostra ancora una riga di avviso
— David Ching

35

Il mio modo doppio di affrontarlo.

Salvalo in una variabile di scarto (C # 7)

Esempio

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Dall'introduzione dei rigetti in C # 7, ora ritengo che sia meglio che sopprimere l'avvertimento. Perché non solo sopprime l'avvertimento, ma rende anche chiara l'intenzione di fuoco e dimentica.

Inoltre, il compilatore sarà in grado di ottimizzarlo in modalità di rilascio.

Sopprimilo e basta

#pragma warning disable 4014
...
#pragma warning restore 4014

è una soluzione abbastanza piacevole per "sparare e dimenticare".

Il motivo per cui esiste questo avviso è perché in molti casi non è tua intenzione utilizzare un metodo che restituisce attività senza attenderlo. Sopprimere l'avviso quando si intende sparare e dimenticare ha senso.

Se hai difficoltà a ricordare come si scrive #pragma warning disable 4014, lascia che Visual Studio lo aggiunga per te. Premi Ctrl +. per aprire "Azioni rapide" e quindi "Sopprimi CS2014"

Tutto sommato

È stupido creare un metodo che richiede qualche spunta in più per essere eseguito, allo scopo di sopprimere un avviso.


Funzionava con Visual Studio per Mac 7.0.1 (build 24).
— IronRod

1
È stupido creare un metodo che richiede qualche altro[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
— segno di spunta

1
@Noseratio Molte volte quando uso AggressiveInliningil compilatore lo
— ignoro

1
Mi piace l'opzione pragma, perché è super semplice e si applica solo alla riga (o sezione) di codice corrente, non a un intero metodo.
— wasatchwizard,

2
Non dimenticare di utilizzare il codice di errore come #pragma warning disable 4014e quindi ripristinare l'avviso in seguito con #pragma warning restore 4014. Funziona ancora senza il codice di errore, ma se non aggiungi il numero di errore eliminerà tutti i messaggi.
— DunningKrugerEffect,

11

Un modo semplice per interrompere l'avviso è semplicemente assegnare l'attività quando la si chiama:

Task fireAndForget = WorkAsync(); // No warning now

E così nel tuo post originale faresti:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Ho citato questo approccio nella domanda stessa, come uno di quelli che non mi sono particolarmente piaciuti.
— noseratio

Ops! Non l'ho notato perché era nella stessa sezione di codice del tuo pragma uno ... E stavo cercando risposte. A parte questo, cosa non ti piace di questo metodo?
— Noelicus,

1
Non mi piace che tasksembri una variabile locale dimenticata. Quasi come se il compilatore dovesse darmi un altro avvertimento, qualcosa del tipo " taskè assegnato ma il suo valore non è mai usato", inoltre non lo fa. Inoltre, rende il codice meno leggibile. Io stesso uso questo approccio.
— noseratio,

Abbastanza giusto - ho avuto una sensazione simile ed è per questo che lo chiamo fireAndForget... quindi mi aspetto che d'ora in poi sia senza riferimenti.
— Noelicus,

4

Il motivo dell'avviso è che WorkAsync sta restituendo a Task che non viene mai letto o atteso. È possibile impostare il tipo di restituzione di WorkAsync su voide l'avviso scomparirà.

In genere un metodo restituisce a Taskquando il chiamante deve conoscere lo stato del lavoratore. Nel caso di un incendio e dimentica, il vuoto dovrebbe essere restituito per assomigliare al fatto che il chiamante è indipendente dal metodo chiamato.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

2

Perché non avvolgerlo in un metodo asincrono che restituisce il vuoto? Un po 'lungo ma vengono utilizzate tutte le variabili.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

Ho trovato questo approccio per caso oggi. È possibile definire un delegato e assegnare prima il metodo asincrono al delegato.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

e chiamalo così

(new IntermediateHandler(AsyncOperation))();

...

Ho pensato che fosse interessante che il compilatore non avesse dato lo stesso avvertimento quando si utilizzava il delegato.


Non è necessario dichiarare un delegato, ma potresti farlo (new Func<Task>(AsyncOperation))()anche se l'IMO è ancora un po 'prolisso.
— noseratio,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.