166 votes

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

Je viens d'avoir VS2012 et j'essaie de me débrouiller avec async .

Disons que j'ai une méthode qui récupère une valeur à partir d'une source bloquante. Je ne veux pas que l'appelant de la méthode soit bloqué. Je pourrais écrire la méthode pour qu'elle prenne un callback qui est invoqué lorsque la valeur arrive, mais comme j'utilise C# 5, je décide de rendre la méthode asynchrone pour que les appelants n'aient pas à s'occuper des callbacks :

// 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 PromptForStringAsync n'était pas asynchrone, cette méthode nécessiterait d'imbriquer un callback dans un callback. Avec l'asynchronie, je peux écrire ma méthode de 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'à présent, tout va bien. Le problème est que lorsque je appelez GetNameAsync :

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

Le but de GetNameAsync est que c'est asynchrone. Je n'ai pas quiere de la bloquer, car je veux retourner au 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 le fichier GetNameAsync ligne :

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 en cours continue avant que l'appel ne soit terminé". C'est le point de code asynchrone, non ?

Je préfère que mon code soit compilé sans avertissement, mais il n'y a rien à "réparer" ici, car le code fait exactement ce que je veux qu'il fasse. 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é contraint d'écrire ce code inutile, car il supprime l'avertissement normal "valeur jamais utilisée".

Je peux aussi 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 même plus code superflu. Je dois donc écrire du code dont je n'ai pas besoin ou tolérer un avertissement inutile.

Mon utilisation de l'asynchronisme est-elle incorrecte ?

119voto

Nikolay Khil Points 2140

Si vous n'avez vraiment pas besoin du résultat, vous pouvez simplement modifier l'attribut GetNameAsync pour retourner void :

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

Considérer pour voir la réponse à une question connexe : Quelle est la différence entre le retour d'un vide et le retour d'une tâche ?

Mise à jour

Si vous avez besoin du résultat, vous pouvez modifier l'option GetNameAsync pour revenir, dites, 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;
}

105voto

Willem Duncan Points 2206

J'interviens assez tardivement dans cette discussion, mais il existe également la possibilité d'utiliser l'option #pragma la directive du préprocesseur. J'ai ici et là du code asynchrone que je ne veux explicitement pas attendre dans certaines conditions, et je n'aime pas les avertissements et les variables inutilisées, comme vous tous :

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

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

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

Les exceptions lancées pendant 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 l'appel asynchrone à une variable. Cela vous permet d'avoir 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é, les variables de rejet : Rejets - Guide C# qui peut également vous aider à cet égard.

_ = SomeMethodAsync();

51voto

Joe Savage Points 707

Je n'aime pas particulièrement les solutions qui consistent soit à assigner la tâche à une variable inutilisée, soit à modifier la signature de la méthode pour qu'elle renvoie void. La première solution crée du code superflu et non intuitif, tandis que la seconde peut être impossible si vous implémentez une interface ou si vous avez une autre utilisation de la fonction où vous voulez 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. Non seulement cela supprimera tous les avertissements, de ReSharper ou autre, mais cela rend le code plus compréhensible, et indique aux futurs mainteneurs 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) { }
}

Utilisation :

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

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

31voto

MrEs Points 96

async void EST MAUVAIS !

  1. Quelle est la différence entre le retour d'un vide et le retour d'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 la commande Task via une méthode anonyme...

par exemple

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

Ou si vous voulez que cela bloque, vous pouvez attendre sur la méthode anonyme.

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

Toutefois, si votre GetNameAsync doit interagir avec l'interface utilisateur ou même tout ce qui est lié à l'interface utilisateur (WINRT/MVVM, je vous regarde), alors cela devient un peu plus funky =)

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

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

Puis, dans votre méthode asynchrone, vous devrez interagir avec votre interface utilisateur ou les éléments liés à l'interface utilisateur en pensant à ce répartiteur...

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

18voto

Jonathan Allen Points 23540

C'est ce que je suis en train de faire :

SomeAyncFunction().RunConcurrently();

RunConcurrently est 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/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X