La documentation MSDN semble indiquer que async
et await
conviennent aux tâches liées aux E/S tandis que Task.Run
doit être utilisé pour les tâches liées au processeur.
Je travaille sur une application qui exécute des requêtes HTTP pour récupérer des documents HTML, qu'elle analyse ensuite. J'ai une méthode qui ressemble à ceci:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound
}
Est-ce une bonne utilisation appropriée de async
et await
, ou est-ce que je l'utilise de manière excessive?
Il y a déjà deux bonnes réponses, mais pour ajouter mon 0.02 ...
Si vous parlez de consommer des opérations asynchrones, async
/await
fonctionne parfaitement pour les deux E/S liées et lié au processeur.
Je pense que les documents MSDN ont une légère inclinaison vers la production d'opérations asynchrones, auquel cas vous voulez utiliser TaskCompletionSource
(ou similaire ) pour les E/S et Task.Run
(ou similaire) pour le processeur. Une fois que vous avez créé le wrapper initial de Task
, il est préférable consommé par async
et await
.
Pour votre exemple particulier, cela dépend vraiment du temps que prendra LoadHtmlDocument
. Si vous supprimez le Task.Run
, vous l'exécuterez dans le même contexte qui appelle LoadPage
(éventuellement sur un thread d'interface utilisateur). Les directives de Windows 8 spécifient que toute opération prenant plus de 50 ms doit être effectuée async
... en gardant à l'esprit que 50 ms sur votre machine de développeur peuvent être plus longues sur la machine d'un client ...
Donc, si vous pouvez garantir que LoadHtmlDocument
fonctionnera pendant moins de 50 ms, vous pouvez simplement l'exécuter directement:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound
return LoadHtmlDocument(contentStream); //CPU-bound
}
Cependant, je recommanderais ConfigureAwait
comme @svick l'a mentionné:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)
.ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync()
.ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
return LoadHtmlDocument(contentStream); //CPU-bound
}
Avec ConfigureAwait
, si la requête HTTP ne se termine pas immédiatement (de manière synchrone), cela entraînera (dans ce cas) LoadHtmlDocument
à être exécuté sur un thread de pool de threads sans appel explicite à Task.Run
.
Si vous êtes intéressé par les performances de async
à ce niveau, vous devriez consulter vidéo et article MSDN de Stephen Toub sur le sujet. Il a des tonnes d'informations utiles.
Il convient à await
toute opération asynchrone (c'est-à-dire représentée par un Task
).
Le point clé est que pour les opérations IO, dans la mesure du possible, vous souhaitez utiliser une méthode fournie qui est, à la base, asynchrone, plutôt que d'utiliser Task.Run
sur une méthode synchrone bloquante. Si vous bloquez un thread (même un thread de pool de threads) lors de l'exécution d'E/S, vous ne tirez pas parti de la puissance réelle du modèle await
.
Une fois que vous avez créé un Task
qui représente votre opération, vous ne vous souciez plus si c'est un processeur ou IO lié. Pour l'appelant, c'est juste une opération asynchrone qui doit être await
- ed.
Il y a plusieurs choses à considérer:
Task.Run()
est probablement une bonne idée. Bien que les utilisateurs de votre code puissent le faire eux-mêmes, s'ils le souhaitent.Task.Run()
nécessite de la surcharge au lieu d'exécuter le code directement, mais cela ne devrait pas être significatif si l'opération prend un certain temps. (De plus, le retour au contexte de synchronisation entraîne des frais supplémentaires, ce qui est une raison supplémentaire pour laquelle vous devez utiliser ConfigureAwait(false)
pour la plupart des await
dans votre code de bibliothèque.)Je pense que l'utilisation de await Task.Run()
est le bon choix ici. Il présente certains frais généraux, mais aussi certains avantages, qui peuvent être importants.