web-dev-qa-db-fra.com

Quelles sont les différences entre utiliser ConfigureAwait (false) et Task.Run?

Je comprends qu'il est recommandé d'utiliser ConfigureAwait(false) pour awaits 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.

Exemple avec 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
}

Exemple avec 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?

55
Sam

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.

68
Stephen Cleary

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.

16
Reed Copsey

Réponse d'accord @Stephen. Si la confusion persiste, voir les captures d'écran ci-dessous
Voir ci-dessous image Fil principal essayant de mettre à jour Label  enter image description here

2 # Avec ConfigureAwait (false)
Voir ci-dessous le fil de travail image essayant de mettre à jour l'étiquette  enter image description here

3
Pankaj Rawat

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
}
1
Roman Gudkov