avertissement cet appel n'est pas attendu, l'exécution de la méthode courante continue


135

Je viens de recevoir VS2012 et j'essaie de comprendre async.

Disons que j'ai une méthode qui récupère une valeur à partir d'une source de blocage. Je ne veux pas que l'appelant de la méthode bloque. Je pourrais écrire la méthode pour prendre un rappel qui est appelé lorsque la valeur arrive, mais comme j'utilise C # 5, je décide de rendre la méthode asynchrone afin que les appelants n'aient pas à gérer les rappels:

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

Voici un exemple de méthode qui l'appelle. Si ce PromptForStringAsyncn'était pas asynchrone, cette méthode nécessiterait l'imbrication d'un rappel dans un rappel. Avec async, j'écris ma méthode de cette manière très naturelle:

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

Jusqu'ici tout va bien. Le problème est lorsque j'appelle GetNameAsync:

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

Tout l'intérêt de GetNameAsyncest qu'il est asynchrone. Je ne veux pas qu'il se bloque, car je veux revenir à MainWorkOfApplicationIDontWantBlocked dès que possible et laisser GetNameAsync faire son travail en arrière-plan. Cependant, l'appeler de cette façon me donne un avertissement du compilateur sur la GetNameAsyncligne:

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.

Je suis parfaitement conscient que "l'exécution de la méthode actuelle se poursuit avant que l'appel ne soit terminé". C'est le but du code asynchrone, non?

Je préfère que mon code compile sans avertissements, mais il n'y a rien à "corriger" ici car le code fait exactement ce que je compte faire. Je peux me débarrasser de l'avertissement en stockant la valeur de retour de GetNameAsync:

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

Mais maintenant, j'ai du code superflu. Visual Studio semble comprendre que j'ai été obligé d'écrire ce code inutile, car il supprime l'avertissement normal «valeur jamais utilisée».

Je peux également me débarrasser de l'avertissement en enveloppant GetNameAsync dans une méthode qui n'est pas asynchrone:

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

Mais c'est du code encore plus superflu. Je dois donc écrire du code dont je n'ai pas besoin ou que je tolère un avertissement inutile.

Y a-t-il quelque chose dans mon utilisation de l'async qui ne va pas ici?


2
BTW, lors de la mise en œuvre, PromptForStringAsyncvous faites plus de travail que nécessaire; renvoyez simplement le résultat de Task.Factory.StartNew. C'est déjà une tâche dont la valeur est la chaîne saisie dans la console. Il n'est pas nécessaire de l'attendre pour renvoyer le résultat; cela n'ajoute aucune valeur nouvelle.
Servy

Wwouldn't - il plus judicieux de GetNameAsyncfournir le nom complet qui a été fourni par l'utilisateur ( par exemple Task<Name>, plutôt que de retourner un Task? DoStuffPourrait alors stocker cette tâche, et soit awaitil après l'autre méthode, ou même passer la tâche à l'autre méthode de sorte qu'il pourrait awaitou Waitquelque part à l' intérieur de la mise en œuvre de ce.
Servy

@Servy: Si je viens de renvoyer la tâche, j'obtiens une erreur "Comme il s'agit d'une méthode asynchrone, l'expression de retour doit être de type 'string' plutôt que 'Task <string>'".
Mud

1
Supprimez le asyncmot - clé.
Servy

13
OMI, c'était un mauvais choix pour un avertissement de la part de l'équipe C #. Les avertissements devraient concerner des choses qui sont certainement fausses. Il y a beaucoup de cas où vous voulez "déclencher et oublier" une méthode asynchrone, et d'autres fois où vous voulez réellement l'attendre.
MgSam

Réponses:


103

Si vous n'avez vraiment pas besoin du résultat, vous pouvez simplement changer la GetNameAsyncsignature de pour retourner void:

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

Pensez à voir la réponse à une question connexe: Quelle est la différence entre renvoyer une annulation et renvoyer une tâche?

Mettre à jour

Si vous avez besoin du résultat, vous pouvez modifier le GetNameAsyncpour renvoyer, par exemple 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;
}

Et utilisez-le comme suit:

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
Ce ne serait le cas que s'il ne se soucie jamais du résultat, plutôt que de ne pas avoir besoin du résultat cette fois-ci .
Servy

3
@Servy, d'accord, merci pour la clarification. Mais OP GetNameAsyncne retourne aucune valeur (sauf le résultat lui-même, bien sûr).
Nikolay Khil

2
Correct, mais en renvoyant une tâche, il peut savoir quand il a terminé d'exécuter toutes les opérations asynchrones. S'il revient void, il n'a aucun moyen de savoir quand c'est fait. C'est ce que je voulais dire quand j'ai dit «le résultat» dans mon commentaire précédent.
Servy le

29
En règle générale, vous ne devriez jamais avoir de async voidméthode à l'exception des gestionnaires d'événements.
Daniel Mann

2
Une autre chose à noter ici est que le comportement est différent en ce qui concerne les exceptions non observées lorsque vous passez à async voidune exception que vous n'attrapez pas, cela plantera votre processus, mais dans .net 4.5, il continuera à fonctionner.
Caleb Vear

62

Je suis assez en retard dans cette discussion, mais il y a aussi la possibilité d'utiliser la #pragmadirective pré-processeur. J'ai du code asynchrone ici et là que je ne veux explicitement pas attendre dans certaines conditions, et je n'aime pas les avertissements et les variables inutilisées, tout comme vous:

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

Le "4014"provient de cette page MSDN: Avertissement du compilateur (niveau 1) CS4014 .

Voir également l'avertissement / réponse de @ ryan-horath ici https://stackoverflow.com/a/12145047/928483 .

Les exceptions levées lors d'un appel asynchrone qui n'est pas attendu seront perdues. Pour vous débarrasser de cet avertissement, vous devez affecter la valeur de retour de tâche de l'appel asynchrone à une variable. Cela garantit que vous avez accès à toutes les exceptions levées, qui seront indiquées dans la valeur de retour.

Mise à jour pour C # 7.0

C # 7.0 ajoute une nouvelle fonctionnalité, supprimer les variables: Discards - Guide C # , qui peut également aider à cet égard.

_ = SomeMethodAsync();

5
C'est absolument génial. Je ne savais pas que tu pouvais faire ça. Je vous remercie!
Maxim Gershkovich

1
Il n'est même pas nécessaire de dire var, il suffit d'écrire_ = SomeMethodAsync();
Ray

41

Je n'aime pas particulièrement les solutions qui affectent la tâche à une variable inutilisée ou modifient la signature de la méthode pour renvoyer void. Le premier crée un code superflu et non intuitif, tandis que le second peut ne pas être possible si vous implémentez une interface ou si vous avez une autre utilisation de la fonction où vous souhaitez utiliser la tâche retournée.

Ma solution est de créer une méthode d'extension de Task, appelée DoNotAwait () qui ne fait rien. Cela supprimera non seulement tous les avertissements, ReSharper ou autre, mais rendra le code plus compréhensible et indiquera aux futurs responsables de votre code que vous aviez vraiment l'intention que l'appel ne soit pas attendu.

Méthode d'extension:

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

Usage:

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

Modifié pour ajouter: c'est similaire à la solution de Jonathan Allen où la méthode d'extension commencerait la tâche si elle n'était pas déjà commencée, mais je préfère avoir des fonctions à usage unique afin que l'intention de l'appelant soit complètement claire.


1
J'aime ça mais je l'ai renommé Unawait ();)
Ostati

29

async void EST MAUVAIS!

  1. Quelle est la différence entre renvoyer un nul et renvoyer une tâche?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Ce que je suggère, c'est que vous exécutiez explicitement le Taskvia une méthode anonyme ...

par exemple

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

Ou si vous vouliez le bloquer, vous pouvez attendre sur la méthode anonyme

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

Cependant, si votre GetNameAsyncméthode doit interagir avec l'interface utilisateur ou même quoi que ce soit lié à l'interface utilisateur, (WINRT / MVVM, je vous regarde), alors cela devient un peu plus funk =)

Vous devrez transmettre la référence au répartiteur de l'interface utilisateur comme ceci ...

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

Et puis, dans votre méthode asynchrone, vous devrez interagir avec votre interface utilisateur ou les éléments liés à l'interface utilisateur pensant que le répartiteur ...

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

Votre extension de tâche est géniale. Je ne sais pas pourquoi je n'en ai pas implémenté auparavant.
Mikael Dúi Bolinder

8
La première approche que vous mentionnez provoque un avertissement différent: 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. cela entraîne également la création d'un nouveau thread, alors qu'un nouveau thread ne sera pas nécessairement créé avec async / await seul.
gregsdennis

Vous devriez être debout monsieur!
Ozkan

16

C'est ce que je fais actuellement:

SomeAyncFunction().RunConcurrently();

RunConcurrentlyest défini comme ...

 /// <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. Cela semble éviter que tous les problèmes ne soient comparés dans les commentaires d'autres réponses. Merci.
Grault

3
Vous avez juste besoin de ceci: public static void Forget(this Task task) { }
Shital Shah

1
que voulez-vous dire là-bas @ShitalShah
Jay Wick

@ShitalShah qui ne fonctionnera qu'avec des tâches à démarrage automatique telles que celles créées avec async Task. Certaines tâches doivent être démarrées manuellement.
Jonathan Allen

7

Selon l'article de Microsoft sur cet avertissement, vous pouvez le résoudre en affectant simplement la tâche renvoyée à une variable. Vous trouverez ci-dessous une traduction du code fourni dans l'exemple 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);

Notez que cela entraînera le message «La variable locale n'est jamais utilisée» dans ReSharper.


Oui, cela supprime l'avertissement, mais non, cela ne résout vraiment rien. Task-les fonctions de retour devraient être await-ed à moins que vous n'ayez une très bonne raison de ne pas le faire. Il n'y a aucune raison ici pour que le rejet de la tâche soit mieux que la réponse déjà acceptée d'utiliser une async voidméthode.

Je préfère également la méthode du vide. Cela rend StackOverflow plus intelligent que Microsoft, mais bien sûr, cela devrait être acquis.
devlord

4
async voidintroduit de graves problèmes autour de la gestion des erreurs et entraîne un code non testable (voir mon article MSDN ). Il serait de loin préférable d'utiliser la variable - si vous êtes absolument sûr de vouloir que les exceptions soient avalées en silence. Plus vraisemblablement, l’opérateur voudrait démarrer deux Tasks, puis faire un await Task.WhenAll.
Stephen Cleary

@StephenCleary Il est possible de configurer si les exceptions des tâches non observées sont ignorées. S'il y a une chance que votre code soit utilisé dans le projet de quelqu'un d'autre, ne laissez pas cela se produire. Une async void DoNotWait(Task t) { await t; }méthode d'assistance simple peut être utilisée pour éviter les inconvénients des async voidméthodes que vous décrivez. (Et je ne pense pas que Task.WhenAllce soit ce que veut l'OP, mais cela pourrait très bien l'être.)

@hvd: Votre méthode d'assistance la rendrait testable, mais elle aurait toujours un support de gestion des exceptions très médiocre.
Stephen Cleary

3

Ici, une solution simple.

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

Cordialement


1

C'est votre exemple simplifié qui provoque le code superfleux. Normalement, vous voudriez utiliser les données qui ont été extraites de la source de blocage à un moment donné dans le programme, donc vous voudriez que le résultat soit renvoyé afin qu'il soit possible d'accéder aux données.

Si vous avez vraiment quelque chose qui se passe totalement isolé du reste du programme, async ne serait pas la bonne approche. Démarrez simplement un nouveau fil pour cette tâche.


Je ne veux utiliser les données extraites de la source de blocage, mais je ne veux pas bloquer l'appelant pendant que je l' attends. Sans async, vous pouvez accomplir cela en passant un rappel. Dans mon exemple, j'ai deux méthodes asynchrones qui doivent être appelées en séquence, et le deuxième appel doit utiliser une valeur retournée par le premier. Cela signifierait imbriquer des gestionnaires de rappel, ce qui devient laid comme l'enfer. La documentation que j'ai lue suggère que c'est précisément ce qui a asyncété conçu pour nettoyer ( par exemple )
Mud

@Mud: Envoyez simplement le résultat du premier appel asynchrone en tant que paramètre au deuxième appel. De cette façon, le code de la deuxième méthode démarre tout de suite et il peut attendre le résultat du premier appel quand il en a envie.
Guffa

Je peux le faire, mais sans asynchrone, cela signifie des rappels imbriqués, comme ceci: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })même dans cet exemple trivial, c'est une salope à analyser. Avec async, un code équivalent est généré pour moi lorsque j'écris, result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); ce qui est beaucoup plus facile à lire (bien qu'aucun des deux ne soit très lisible brisé ensemble dans ce commentaire!)
Mud

@Mud: Oui. Mais vous pouvez appeler la deuxième méthode asynchrone juste après la première, il n'y a aucune raison pour qu'elle attende l'appel synchrone à Use.
Guffa

Je ne comprends vraiment pas ce que vous dites. MethodWithCallback est asynchrone. Si je les rappelle dos à dos sans attendre le premier appel, le deuxième appel peut se terminer avant le premier. Cependant, j'ai besoin du résultat du premier appel dans le gestionnaire pour le second. Je dois donc attendre le premier appel.
Mud

0

Voulez-vous vraiment ignorer le résultat? comme en ignorant les exceptions inattendues?

Sinon, vous voudrez peut-être jeter un coup d'œil à cette question: approche Fire and Forget ,


0

Si vous ne souhaitez pas modifier la signature de la méthode à renvoyer void(car le retour voiddoit toujours être un void ed), vous pouvez utiliser la fonctionnalité C # 7.0+ Discard comme celle-ci, qui est légèrement meilleure que l'assignation à une variable (et devrait supprimer la plupart des autres avertissements des outils de validation source):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.