Je viens de recevoir VS2012 et j'essaie de maîtriser async
.
Disons que j'ai une méthode qui extrait de la valeur d'une source bloquante. 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ée 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 PromptForStringAsync
n'é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 quand je callGetNameAsync:
public static void DoStuff()
{
GetNameAsync();
MainWorkOfApplicationIDontWantBlocked();
}
L’intérêt de GetNameAsync
est qu’il est asynchrone. Je ne veuxque bloquer, car je veux revenir au MainWorkOfApplicationIDontWantBlocked ASAP et laisser GetNameAsync faire son travail en arrière-plan. Cependant, l'appel de cette façon me donne un avertissement du compilateur sur la ligne GetNameAsync
:
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 continue avant que l'appel ne soit terminé". C'est le pointdu code asynchrone, non?
Je préfère que mon code soit compilé sans avertissements, mais il n'y a rien de "correctif" 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 éliminer l'avertissement en encapsulant GetNameAsync dans une méthode non asynchrone:
public static Task GetNameWrapper()
{
return GetNameAsync();
}
Mais c'est même pluscode superflu. Donc je dois écrire du code dont je n'ai pas besoin ou tolérer un avertissement inutile.
Y a-t-il quelque chose qui cloche dans mon utilisation d'async?
Si vous n'avez vraiment pas besoin du résultat, vous pouvez simplement changer la signature de GetNameAsync
pour renvoyer void
:
public static async void GetNameAsync()
{
...
}
Pensez à voir la réponse à une question connexe: Quelle est la différence entre retourner un vide et retourner une tâche?
Mettre à jour
Si vous avez besoin du résultat, vous pouvez modifier la GetNameAsync
pour qu'elle retourne, 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;
}
Je suis assez en retard pour cette discussion, mais il y a aussi l'option d'utiliser la directive #pragma
pré-processeur. J'ai un code async ici et là que je ne veux pas explicitement attendre dans certaines conditions, et je n'aime pas les avertissements et les variables non utilisées, tout comme le reste de vous
#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014
Le "4014"
provient de cette page MSDN: Avertissement du compilateur (niveau 1) CS4014 .
Voir aussi l'avertissement/réponse de @ ryan-horath ici https://stackoverflow.com/a/12145047/928483 .
Les exceptions levées lors d'un appel asynchrone non attendu seront perdues. Pour vous débarrasser de cet avertissement, vous devez affecter la valeur de retour de la 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.
Je ne suis pas particulièrement friand des solutions qui attribuent la tâche à une variable non utilisée ou qui modifient la signature de la méthode pour rendre void. Le premier crée un code superflu et non intuitif, tandis que le dernier risque de ne pas être possible si vous implémentez une interface ou utilisez une autre utilisation de la fonction pour laquelle vous souhaitez utiliser la tâche renvoyée.
Ma solution est de créer une méthode d'extension de tâche, appelée DoNotAwait (), qui ne fait rien. Cela supprimera non seulement tous les avertissements, ReSharper ou autres, mais rendra le code plus compréhensible et indiquera aux futurs responsables de votre code que vous avez vraiment voulu 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();
}
Ajoutés pour ajouter: ceci est similaire à la solution de Jonathan Allen dans laquelle la méthode d'extension lancerait la tâche si ce n'était pas déjà fait, mais je préfère avoir des fonctions à usage unique afin que l'intention de l'appelant soit parfaitement claire.
async void IS BAD!
Quelle est la différence entre retourner un vide et retourner une tâche?http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspxhttp://www.tonicodes.net/blog/why-you-should-almost-nemost-neever-write-void-asynchronous-methods/
Ce que je suggère, c'est que vous exécutiez explicitement la tâche via une méthode anonyme ...
c'est à dire.
public static void DoStuff()
{
Task.Run(async () => GetNameAsync());
MainWorkOfApplicationIDontWantBlocked();
}
Ou si vous voulez qu'il bloque, vous pouvez attendre sur la méthode anonyme
public static void DoStuff()
{
Task.Run(async () => await GetNameAsync());
MainWorkOfApplicationThatWillBeBlocked();
}
Cependant, si votre méthode "GetNameAsync" doit interagir avec l'interface utilisateur ou même avec une interface liée, (WINRT/MVVM, je vous regarde); alors ça devient un peu funkier =)
Vous aurez besoin de passer la référence au répartiteur d'interface utilisateur comme ceci ...
Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));
Et ensuite, dans votre méthode asynchrone, vous devrez interagir avec votre interface utilisateur ou les éléments liés à l'interface utilisateur pensés par ce répartiteur ...
dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { this.UserName = userName; });
C'est ce que je suis en train de faire:
SomeAyncFunction().RunConcurrently();
Où 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();
}
Selon l'article de Microsoft relatif à 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.
C'est votre exemple simplifié qui provoque le code superflous. Normalement, vous souhaiterez utiliser les données extraites de la source de blocage à un moment donné du programme. Vous voudrez donc que le résultat soit renvoyé de manière à pouvoir 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. Il suffit de démarrer un nouveau fil pour cette tâche.
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 ,
Ici, une solution simple.
public static class TasksExtensions
{
public static void RunAndForget(this Task task)
{
}
}
Cordialement