J'essaie de comprendre l'attente asynchrone dans la forme la plus simple. Je veux créer une méthode très simple qui ajoute deux chiffres pour cet exemple, cela n’a aucun temps de traitement, c’est un problème, c’est juste une question de formulation.
private async Task DoWork1Async()
{
int result = 1 + 2;
}
private async Task DoWork2Async()
{
Task.Run( () =>
{
int result = 1 + 2;
});
}
Si j'attends DoWork1Async()
le code sera-t-il exécuté de manière synchrone ou asynchrone?
Dois-je envelopper le code de synchronisation avec Task.Run
pour que la méthode soit attendue ET asynchrone afin de ne pas bloquer le thread d'interface utilisateur?
J'essaie de déterminer si ma méthode est une Task
ou si je retourne Task<T>
dois-je enrouler le code avec Task.Run
pour le rendre asynchrone?.
Question stupide, j'en suis sûr, mais je vois des exemples sur le réseau où des personnes attendent un code qui n'a rien d'async et qui n'est pas encapsulé dans un Task.Run
ou StartNew
.
Commençons par clarifier la terminologie: "asynchrone" (async
) signifie qu’elle peut céder le contrôle au thread appelant avant qu’il ne démarre. Dans une méthode async
, ces points "de rendement" sont des expressions await
.
Ce terme est très différent du terme "asynchrone", car utilisé dans la documentation MSDN pendant des années pour signifier "s'exécute sur un thread d'arrière-plan".
Pour confondre le problème, async
est très différent de "waitable"; il existe certaines méthodes async
dont les types de retour ne sont pas attendus, et de nombreuses méthodes renvoyant des types attendus qui ne sont pas async
.
Assez parlé de ce qu'ils ne sont pas; voici ce qu'ils sont:
async
autorise une méthode asynchrone (c'est-à-dire qu'il autorise les expressions await
). Les méthodes async
peuvent renvoyer Task
, Task<T>
ou (si vous devez) void
.Task
et Task<T>
.Donc, si nous reformulons votre question en "comment puis-je exécuter une opération sur un thread d'arrière-plan d'une manière attendue", la réponse consiste à utiliser Task.Run
:
private Task<int> DoWorkAsync() // No async because the method does not need await
{
return Task.Run(() =>
{
return 1 + 2;
});
}
(Mais ce modèle est une mauvaise approche; voir ci-dessous).
Mais si votre question est "comment créer une méthode async
pouvant retourner à son appelant au lieu de bloquer", la réponse consiste à déclarer la méthode async
et à utiliser await
. "donner" des points:
private async Task<int> GetWebPageHtmlSizeAsync()
{
var client = new HttpClient();
var html = await client.GetAsync("http://www.example.com/");
return html.Length;
}
Ainsi, le schéma de base des choses consiste à faire en sorte que le code async
dépende de "l'attente" dans ses expressions await
. Ces "attentes" peuvent être d’autres méthodes async
ou juste des méthodes régulières renvoyant des attentes. Les méthodes classiques renvoyant Task
/Task<T>
can peuvent utiliser Task.Run
pour exécuter le code sur un thread d'arrière-plan ou (plus généralement) elles peuvent utiliser TaskCompletionSource<T>
ou un de ses raccourcis (TaskFactory.FromAsync
, Task.FromResult
, etc.). Je ne recommande pas d'emballer une méthode entière dans Task.Run
; Les méthodes synchrones doivent avoir des signatures synchrones et il appartient au consommateur de décider si elles doivent être enveloppées dans un Task.Run
:
private int DoWork()
{
return 1 + 2;
}
private void MoreSynchronousProcessing()
{
// Execute it directly (synchronously), since we are also a synchronous method.
var result = DoWork();
...
}
private async Task DoVariousThingsFromTheUIThreadAsync()
{
// I have a bunch of async work to do, and I am executed on the UI thread.
var result = await Task.Run(() => DoWork());
...
}
J'ai un async
/await
intro sur mon blog; à la fin se trouvent de bonnes ressources de suivi. Les documents MSDN pour async
sont exceptionnellement bons, aussi.
Une des choses les plus importantes à retenir lors de la décoration d’une méthode avec async est qu’au moins il existe un attend opérateur à l'intérieur de la méthode. Dans votre exemple, je le traduirais comme indiqué ci-dessous en utilisant TaskCompletionSource .
private Task<int> DoWorkAsync()
{
//create a task completion source
//the type of the result value must be the same
//as the type in the returning Task
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
Task.Run(() =>
{
int result = 1 + 2;
//set the result to TaskCompletionSource
tcs.SetResult(result);
});
//return the Task
return tcs.Task;
}
private async void DoWork()
{
int result = await DoWorkAsync();
}
Lorsque vous utilisez Task.Run pour exécuter une méthode, Task obtient un thread du pool de threads pour exécuter cette méthode. Donc, du point de vue du thread d'interface utilisateur, il est "asynchrone" car il ne bloque pas le thread d'interface utilisateur. Cela convient parfaitement aux applications de bureau car vous n'avez généralement pas besoin de beaucoup de threads pour prendre en charge les interactions utilisateur.
Toutefois, pour les applications Web, chaque requête est traitée par un thread de pool de threads. Par conséquent, le nombre de requêtes actives peut être augmenté en enregistrant ces threads. L'utilisation fréquente de threads de pool de threads pour simuler une opération asynchrone n'est pas évolutive pour les applications Web.
True Async n'implique pas nécessairement l'utilisation d'un thread pour les opérations d'E/S, telles que l'accès aux fichiers/bases de données, etc. Vous pouvez lire ceci pour comprendre pourquoi les opérations d'E/S n'ont pas besoin de threads. http://blog.stephencleary.com/2013/11/there-is-nnththead.html
Dans votre exemple simple, il s’agit d’un calcul purement lié au processeur. L’utilisation de Task.Run convient donc.