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.