Je comprends qu'il est recommandé d'utiliser ConfigureAwait(false)
pour await
s dans le code de la bibliothèque afin que le code suivant ne s'exécute pas dans le contexte d'exécution de l'appelant, qui pourrait être un thread d'interface utilisateur. Je comprends aussi que await Task.Run(CpuBoundWork)
devrait être utilisé à la place de CpuBoundWork()
pour la même raison.
ConfigureAwait
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address).ConfigureAwait(false))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync().ConfigureAwait(false))
return LoadHtmlDocument(contentStream); //CPU-bound
}
Task.Run
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
return await Task.Run(async () =>
{
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
});
}
Quelles sont les différences entre ces deux approches?
Lorsque vous dites Task.Run
, vous indiquez que le travail de votre processeur peut prendre beaucoup de temps. Il doit donc toujours être exécuté sur un thread de pool de threads.
Lorsque vous dites ConfigureAwait(false)
, vous dites que le reste de cette méthode async
n'a pas besoin du contexte d'origine. ConfigureAwait
est plus un indice d'optimisation; cela ne signifie pas toujours que la poursuite est exécutée sur un thread de pool de threads.
Dans ce cas, votre version Task.Run
aura un peu plus de temps système, car le premier appel en attente (await client.GetAsync(address)
) sera toujours réintégré dans le contexte de l'appel, de même que les résultats de l'appel Task.Run
.
Dans le premier exemple, en revanche, votre première méthode Async()
est configurée pour ne pas nécessiter de marshaler dans le contexte d'appel, ce qui permet de continuer à exécuter la poursuite sur un thread en arrière-plan. En tant que tel, il n'y aura pas de marshaling dans le contexte de l'appelant.
Notez également que, dans les deux cas, LoadPage()
pourrait bloquer encore votre thread d'interface utilisateur, car await client.GetAsync(address)
a besoin de temps pour créer une tâche à transmettre à ConfigureAwait(false)
. Et votre opération fastidieuse a peut-être déjà commencé avant que la tâche ne soit renvoyée.
Une solution possible consiste à utiliser SynchronizationContextRemover
à partir de ici :
public async Task<HtmlDocument> LoadPage(Uri address)
{
await new SynchronizationContextRemover();
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
}