avviso questa chiamata non è attesa, l'esecuzione del metodo corrente continua


135

Ho appena ricevuto VS2012 e sto provando a gestire async.

Diciamo che ho un metodo che recupera un valore da una fonte bloccante. Non voglio bloccare il chiamante del metodo. Potrei scrivere il metodo per prendere un callback che viene invocato quando arriva il valore, ma poiché sto usando C # 5, decido di rendere il metodo asincrono in modo che i chiamanti non debbano gestire i callback:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Ecco un metodo di esempio che lo chiama. Se PromptForStringAsyncnon fosse asincrono, questo metodo richiederebbe l'annidamento di una richiamata all'interno di una richiamata. Con async, riesco a scrivere il mio metodo in questo modo molto naturale:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Fin qui tutto bene. Il problema è quando chiamo GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

L'intero punto di GetNameAsync è che è asincrono. Non voglio che si blocchi, perché voglio tornare a MainWorkOfApplicationIDontWantBlocked al più presto e lasciare che GetNameAsync faccia le sue cose in background. Tuttavia, chiamarlo in questo modo mi dà un avvertimento compilatore sulla GetNameAsynclinea:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Sono perfettamente consapevole che "l'esecuzione del metodo corrente continua prima che la chiamata venga completata". Questo è il punto del codice asincrono, giusto?

Preferisco che il mio codice venga compilato senza avvisi, ma non c'è nulla da "correggere" qui perché il codice sta facendo esattamente quello che intendo. Posso eliminare l'avviso memorizzando il valore restituito diGetNameAsync :

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Ma ora ho un codice superfluo. Visual Studio sembra capire che sono stato costretto a scrivere questo codice non necessario, perché sopprime il normale avviso "valore mai usato".

Posso anche liberarmi dell'avvertimento avvolgendo GetNameAsync in un metodo che non è asincrono:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Ma questo è pari più codice superfluo. Quindi devo scrivere il codice che non mi serve o tollero un avvertimento non necessario.

C'è qualcosa nel mio uso di asincrono che è sbagliato qui?


2
A proposito, durante l'implementazione PromptForStringAsyncfai più lavoro del necessario; restituisci solo il risultato di Task.Factory.StartNew. È già un'attività il cui valore è la stringa immessa nella console. Non è necessario attendere che ritorni il risultato; così facendo non si aggiunge alcun nuovo valore.
Servito il

Wwouldn't più senso per GetNameAsyncfornire il nome completo che è stato fornito dall'utente (ad esempio Task<Name>, piuttosto che restituire un Task? DoStuffPotrebbe quindi memorizzare tale compito, e sia awaitesso dopo l'altro metodo, o anche passare il compito di quell'altra metodo in modo che poteva awaito Waitda qualche parte all'interno di esso di implementazione.
Servy

@Servy: se restituisco semplicemente l'attività, viene visualizzato un errore "Poiché si tratta di un metodo asincrono, l'espressione di ritorno deve essere di tipo 'stringa' anziché 'Attività <stringa>'".
Fango

1
Rimuovi la asyncparola chiave.
Servito il

13
IMO, questa è stata una cattiva scelta per un avvertimento da parte del team C #. Gli avvertimenti dovrebbero essere per cose che sono quasi certamente sbagliate. Ci sono molti casi in cui si desidera "sparare e dimenticare" un metodo asincrono, e altre volte in cui si vuole realmente aspettarlo.
MgSam,

Risposte:


103

Se davvero non hai bisogno del risultato, puoi semplicemente cambiare la GetNameAsyncfirma per restituire void:

public static async void GetNameAsync()
{
    ...
}

Considera di vedere la risposta a una domanda correlata: qual è la differenza tra la restituzione del nulla e la restituzione di un'attività?

Aggiornare

Se è necessario il risultato, è possibile modificare il GetNameAsyncda restituire, ad esempio Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

E usalo come segue:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

15
Questo sarebbe il caso solo se non si preoccupasse mai del risultato, invece di non aver bisogno del risultato questa volta .
Servito il

3
@Servy, giusto, grazie per il chiarimento. Ma gli OP GetNameAsyncnon restituiscono alcun valore (tranne il risultato stesso, ovviamente).
Nikolay Khil,

2
Corretto, ma restituendo un'attività può sapere quando termina l'esecuzione di tutte le operazioni asincrone. Se ritorna void, non ha modo di sapere quando è finito. Questo è ciò che intendevo quando ho detto "il risultato" nel mio commento precedente.
Serve il

29
Come regola generale, non si dovrebbe mai avere un async voidmetodo ad eccezione dei gestori di eventi.
Daniel Mann,

2
Un'altra cosa da notare qui è che il comportamento è diverso quando si tratta di eccezioni non osservate quando si passa a async voidqualsiasi eccezione che non si intercetta arresta il processo, ma in .net 4.5 continuerà a funzionare.
Caleb Vear,

62

Sono in ritardo a questa discussione, ma c'è anche la possibilità di utilizzare la #pragmadirettiva pre-processore. Ho un codice asincrono qua e là che esplicitamente non voglio aspettare in alcune condizioni e non mi piacciono gli avvisi e le variabili non utilizzate proprio come il resto di voi:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

Il "4014"proviene da questa pagina MSDN: compilatore di avviso (livello 1) CS4014 .

Vedi anche l'avviso / risposta di @ ryan-horath qui https://stackoverflow.com/a/12145047/928483 .

Le eccezioni generate durante una chiamata asincrona non attesa andranno perse. Per eliminare questo avviso, è necessario assegnare il valore restituito dall'attività della chiamata asincrona a una variabile. Ciò garantisce l'accesso a tutte le eccezioni generate, che verranno indicate nel valore restituito.

Aggiornamento per C # 7.0

C # 7.0 aggiunge una nuova funzionalità, elimina le variabili: Scarta - Guida di C # , che può anche aiutare in questo senso.

_ = SomeMethodAsync();

5
È assolutamente fantastico. Non avevo idea che tu potessi farlo. Grazie!
Maxim Gershkovich,

1
Non è nemmeno necessario dire var, basta scrivere_ = SomeMethodAsync();
Ray

41

Non sono particolarmente affezionato alle soluzioni che assegnano l'attività a una variabile non utilizzata o cambiano la firma del metodo per restituire il vuoto. Il primo crea codice superfluo e non intuitivo, mentre il secondo potrebbe non essere possibile se si sta implementando un'interfaccia o si ha un altro utilizzo della funzione in cui si desidera utilizzare l'attività restituita.

La mia soluzione è quella di creare un metodo di estensione di Task, chiamato DoNotAwait () che non fa nulla. Questo non solo sopprimerà tutti gli avvisi, ReSharper o altro, ma renderà il codice più comprensibile e indicherà ai futuri manutentori del codice che in realtà intendevi che la chiamata non fosse attesa.

Metodo di estensione:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Uso:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Modificato per aggiungere: questo è simile alla soluzione di Jonathan Allen in cui il metodo di estensione avvierebbe l'attività se non fosse già avviato, ma preferisco avere funzioni a scopo singolo in modo che l'intento del chiamante sia completamente chiaro.


1
Mi piace, ma l'ho rinominato in Unawait ();)
Ostati,

29

async void È CATTIVO!

  1. Qual è la differenza tra la restituzione del nulla e la restituzione di un'attività?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Quello che suggerisco è che tu esegua esplicitamente Tasktramite un metodo anonimo ...

per esempio

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Oppure, se lo volevi bloccare, puoi aspettare con il metodo anonimo

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Tuttavia, se il tuo GetNameAsync metodo deve interagire con l'interfaccia utente o anche qualsiasi elemento associato all'interfaccia utente (WINRT / MVVM, ti sto guardando), allora diventa un po 'più divertente =)

Dovrai passare il riferimento al dispatcher dell'interfaccia utente in questo modo ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

E poi nel tuo metodo asincrono dovrai interagire con la tua UI o gli elementi associati alla UI che pensavano che il dispatcher ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

L'estensione dell'attività è fantastica. Non so perché non ne ho implementato uno prima.
Mikael Dúi Bolinder,

8
Il primo approccio che menzioni provoca un diverso avviso: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. questo provoca anche la creazione di un nuovo thread, mentre un nuovo thread non verrà necessariamente creato con asincrono / wait da solo.
Gregregdennis,

Dovresti essere sveglio signore!
Ozkan,

16

Questo è quello che sto facendo attualmente:

SomeAyncFunction().RunConcurrently();

Dove RunConcurrentlyè definito come ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


1
+1. Sembra che eviti di confrontare tutti i problemi nei commenti di altre risposte. Grazie.
Grault,

3
Hai solo bisogno di questo: public static void Forget(this Task task) { }
Shital Shah,

1
cosa intendi con @ShitalShah
Jay Wick il

@ShitalShah che funzionerà solo con attività di avvio automatico come quelle create con async Task. Alcune attività devono essere avviate manualmente.
Jonathan Allen

7

Secondo l'articolo di Microsoft su questo avviso, è possibile risolverlo semplicemente assegnando l'attività restituita a una variabile. Di seguito è una traduzione del codice fornito nell'esempio di Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Notare che ciò comporterà il messaggio "La variabile locale non viene mai utilizzata" in ReSharper.


Sì, questo sopprime l'avvertimento, ma no, che in realtà non risolve nulla. Task-le funzioni di ritorno dovrebbero essere awaitdisattivate a meno che tu non abbia una buona ragione per non farlo. Non c'è motivo per cui scartare l'attività sarebbe meglio della risposta già accettata dell'uso di un async voidmetodo.

Preferisco anche il metodo vuoto. Questo rende StackOverflow più intelligente di Microsoft, ma ovviamente dovrebbe essere un dato di fatto.
Devlord,

4
async voidintroduce seri problemi nella gestione degli errori e risulta in un codice non testabile (vedi il mio articolo MSDN ). Sarebbe molto meglio usare la variabile - se sei assolutamente sicuro di voler ingoiare le eccezioni silenziosamente. Più probabilmente, l'operazione vorrebbe iniziare due Tasksecondi e poi fare un await Task.WhenAll.
Stephen Cleary,

@StephenCleary Se le eccezioni da attività non osservate vengono ignorate è configurabile. Se è possibile che il tuo codice venga utilizzato nel progetto di qualcun altro, non lasciare che ciò accada. È async void DoNotWait(Task t) { await t; }possibile utilizzare un semplice metodo di supporto per evitare gli svantaggi dei async voidmetodi descritti. (E non credo Task.WhenAllsia quello che vuole l'OP, ma potrebbe benissimo essere.)

@hvd: il tuo metodo di supporto lo renderebbe testabile, ma avrebbe comunque un supporto di gestione delle eccezioni molto scarso.
Stephen Cleary,

3

Ecco una soluzione semplice.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

Saluti


1

È il tuo esempio semplificato che causa il codice superflous. Normalmente si vorrebbe usare i dati che sono stati recuperati dalla fonte bloccante ad un certo punto del programma, quindi si vorrebbe riavere il risultato in modo che fosse possibile ottenere i dati.

Se hai davvero qualcosa che accade totalmente isolato dal resto del programma, async non sarebbe l'approccio giusto. Basta avviare un nuovo thread per tale attività.


Ho fare voglio usare i dati recuperati dalla sorgente blocco, ma io non voglio bloccare il chiamante mentre aspetto di esso. Senza asincrono, è possibile ottenere questo risultato passando un callback. Nel mio esempio, ho due metodi asincroni che devono essere chiamati in sequenza e la seconda chiamata deve utilizzare un valore restituito dalla prima. Ciò significherebbe nidificare i gestori di callback, che diventano brutti da morire. La documentazione che ho letto suggerisce che questo è specificamente ciò che è asyncstato progettato per ripulire ( per esempio )
Fango

@Mud: basta inviare il risultato dalla prima chiamata asincrona come parametro alla seconda chiamata. In questo modo il codice nel secondo metodo inizia immediatamente e può attendere il risultato dalla prima chiamata quando ne ha voglia.
Guffa,

Posso farlo, ma senza asincrono, ciò significa callback nidificati, in questo modo: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })anche in questo banale esempio, è una cagna da analizzare. Con asincrono, quando scrivo codice generato per me viene generato un codice equivalente, result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); che è molto più facile da leggere (anche se nessuno dei due è molto leggibile fracassato in questo commento!)
Fango

@Mud: Sì. Ma puoi chiamare il secondo metodo asincrono subito dopo il primo, non c'è motivo per cui attendere la chiamata sincrona a Use.
Guffa,

Davvero non capisco quello che stai dicendo. MethodWithCallback è asincrono. Se li richiamo schiena contro schiena senza attendere la prima chiamata, la seconda potrebbe terminare prima della prima. Tuttavia, ho bisogno del risultato della prima chiamata nel gestore per la seconda. Quindi devo aspettare alla prima chiamata.
Fango

0

Vuoi davvero ignorare il risultato? come includere l'ignorare eventuali eccezioni impreviste?

Altrimenti potresti dare un'occhiata a questa domanda: approccio Fire and Forget ,


0

Se non si desidera modificare la firma del metodo per restituirla void(poiché il ritorno voiddovrebbe essere sempre nullo ), è possibile utilizzare C # 7.0+ Funzionalità di scarto come questa, che è leggermente meglio dell'assegnazione a una variabile (e dovrebbe rimuovere la maggior parte degli altri avvisi sugli strumenti di convalida della fonte):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
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.