Quelles sont les différences entre l’utilisation de ConfigureAwait (false) et de Task.Run?

Je comprends qu’il est recommandé d’utiliser ConfigureAwait(false) pour l’ await 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 await Task.Run(CpuBoundWork) devrait être utilisé à la place de CpuBoundWork() pour la même raison.

Exemple avec ConfigureAwait

 public async Task 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 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 parlez Task.Run , vous dites que vous avez un certain temps de travail à faire sur le processeur, il doit donc toujours être exécuté sur un thread de pool de threads.

Lorsque vous dites ConfigureAwait(false) , vous dites que le rest de cette méthode async n’a pas besoin du contexte d’origine. ConfigureAwait est plus un conseil d’optimisation; cela ne signifie pas toujours que la suite est exécutée sur un thread de pool de threads.

Dans ce cas, votre version Task.Run aura un peu plus de charge, car le premier appel en attente ( await client.GetAsync(address) ) restituera toujours dans le contexte appelant, tout comme 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 le retour en arrière dans le contexte appelant, ce qui permet à la suite de continuer à s’exécuter sur un thread d’arrière-plan. En tant que tel, il n’y aura aucun retour en arrière dans le contexte de l’appelant.

La réponse @Stephen convenue, Si toujours confusion, voir ci-dessous les captures d’écran 1 # Sans ConfigureAwait (false)
Voir l’image ci-dessous Fil principal essayant de mettre à jour l’étiquette entrer la description de l'image ici

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

En LoadPage() , dans les deux cas, LoadPage() peut toujours bloquer votre thread d’interface utilisateur, car await client.GetAsync(address) besoin de temps pour créer une tâche à transmettre à ConfigureAwait(false) . Et votre opération fastidieuse peut avoir déjà démarré avant que la tâche ne soit renvoyée.

Une solution possible est d’utiliser SynchronizationContextRemover partir d’ ici :

 public async Task 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 }