Meilleure pratique pour appeler ConfigureAwait pour tout le code côté serveur

lorsque vous avez du code côté serveur (i.e. certains ApiController ) et que vos fonctions sont asynchrones - donc ils renvoient Task<SomeObject> - est-il considéré comme une bonne pratique que chaque fois que vous attendez des fonctions que vous appelez ConfigureAwait(false) ?

j'avais lu qu'il est plus performant puisqu'il n'a pas besoin de retourner les contextes thread au contexte thread original. Toutefois, avec ASP.NET Api Web, si votre requête arrive sur un thread, et que vous attendez une fonction et appelez ConfigureAwait(false) qui pourrait potentiellement vous mettre sur un fil différent lorsque vous retournez le résultat final de votre fonction ApiController .

j'ai tapé un exemple de ce dont je parle ci-dessous:

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}
426
demandé sur abatishchev 2012-11-21 12:24:49

4 réponses

mise à jour: ASP.NET Core n'a pas de SynchronizationContext . Si vous êtes sous ASP.NET Core, peu importe que vous utilisiez ConfigureAwait(false) ou non.

pour ASP.NET " complet "ou" classique " ou peu importe, le reste de cette réponse s'applique toujours.

Original post (for non-Core ASP.NET):

cette vidéo par le ASP.NET l'équipe meilleure information sur l'utilisation de async sur ASP.NET.

j'avais lu qu'il est plus performant puisqu'il n'a pas besoin de retourner les contextes thread au contexte thread original.

c'est vrai avec les demandes D'UI, où il n'y a qu'un seul fil D'UI vers lequel vous devez" synchroniser".

In ASP.NET la situation est un peu plus complexe. Lorsqu'une méthode async reprend exécution, il saisit un fil de la ASP.NET piscine de fil. Si vous désactivez la capture de contexte en utilisant ConfigureAwait(false) , alors le thread continue d'exécuter la méthode directement. Si vous ne désactivez pas la capture de contexte, alors le thread rentrera dans le contexte de la requête et continuera à exécuter la méthode.

donc ConfigureAwait(false) ne vous sauve pas un saut de fil dans ASP.NET; il vous épargne la ré-entrée du contexte de la requête, mais cela est normalement très rapide. ConfigureAwait(false) pourrait être utile si vous essayez de faire une petite quantité de traitement parallèle d'une requête, mais vraiment TPL est un meilleur ajustement pour la plupart de ces scénarios.

cependant, avec ASP.NET Api Web, si votre requête arrive sur un thread, et que vous attendez une fonction et appelez ConfigureAwait(false) qui pourrait potentiellement vous mettre sur un thread différent lorsque vous retournez le résultat final de votre fonction ApiController.

en fait, juste faire un await peut faire ça. Une fois que votre méthode async atteint un await , la méthode est bloquée, mais le thread retourne au pool de threads. Quand la méthode est prête à continuer, n'importe quel fil est enlevé de la piscine de fil et utilisé pour reprendre la méthode.

The only difference ConfigureAwait makes in ASP.NET est de savoir si ce fil entre dans le contexte de demande lors de la reprise de la méthode.

j'ai plus d'informations de fond dans mon article MSDN sur SynchronizationContext et mon async intro blog post .

457
répondu Stephen Cleary 2018-01-23 23:23:58

brève réponse à votre question: non. Vous ne devriez pas appeler ConfigureAwait(false) au niveau de l'application comme ça.

TL;DR version de la longue réponse: Si vous écrivez une bibliothèque où vous ne connaissez pas votre consommateur et n'avez pas besoin d'un contexte de synchronisation (ce que vous ne devriez pas dans une bibliothèque je crois), vous devriez toujours utiliser ConfigureAwait(false) . Sinon, les consommateurs de votre bibliothèque peuvent faire face à des blocages en consommant vos méthodes asynchrones de manière bloquante. Ce dépend de la situation.

Voici une explication un peu plus détaillée sur l'importance de la méthode ConfigureAwait (une citation de mon billet de blog):

quand vous attendez sur une méthode avec le mot-clé attente, compilateur génère un tas de code pour vous. L'un des buts de cette l'action est de gérer la synchronisation avec le thread UI (ou main). Clé la composante de cette caractéristique est le SynchronizationContext.Current qui obtient le contexte de synchronisation pour le thread courant. SynchronizationContext.Current est peuplé en fonction de la environnement dans lequel vous êtes. La méthode de travail GetAwaiter recherche SynchronizationContext.Current . Si le contexte de synchronisation actuel est pas null, la suite qui est passé à cet awaiter va obtenir posté de nouveau dans ce contexte de synchronisation.

lors de la consommation d'une méthode, qui utilise le nouveau langage asynchrone caractéristiques, de manière bloquante, vous finirez avec un blocage si vous disposez d'un contexte de synchronisation disponible. Lorsque vous consommez de telles méthodes de manière bloquante (attente sur la tâche avec attente méthode ou prise le résultat directement de la propriété de résultat de la Tâche), vous bloquerez le thread principal en même temps. Lorsque finalement la tâche complète à l'intérieur de cette méthode dans le threadpool, il va invoquer la suite pour renvoyer au fil principal parce que SynchronizationContext.Current est disponible et capturé. Mais il y a un problème ici: le fil UI est bloqué et vous avez un impasse!

aussi, voici deux grands articles pour vous qui sont exactement pour votre question:

enfin, il y a une grande vidéo courte de Lucian Wischik exactement sur ce sujet: méthodes de bibliothèque Async devrait envisager d'utiliser tâche.ConfigureAwait (false) .

Espérons que cette aide.

113
répondu tugberk 2016-04-13 09:12:37

le plus grand inconvénient que J'ai trouvé avec L'utilisation de ConfigureAwait(false) est que la culture de thread est retournée à la valeur par défaut du système. Si vous avez configuré une culture électronique.g ...

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

et vous hébergez sur un serveur dont la culture est définie à en-US, alors vous trouverez avant que ConfigureAwait(false) s'appelle CultureInfo.CurrentCulture retournera en-AU et après vous obtiendrez en-US. c'est à dire

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

si votre application fait tout ce qui nécessite un formatage des données spécifique à la culture, vous devrez en tenir compte lors de L'utilisation de ConfigureAwait(false).

8
répondu Mick 2017-06-22 03:12:32

j'ai quelques réflexions générales sur la mise en œuvre de Task :

  1. tâche est jetable mais nous sommes pas censé utiliser using .
  2. ConfigureAwait a été introduit en 4.5. Task a été introduit dans 4.0.
  3. .NET Threads toujours utilisé à l'écoulement du contexte (voir C# via CLR livre), mais dans l'implémentation par défaut de Task.ContinueWith ils ne sont pas b/c il a été réalisé changement de contexte est cher et il est désactivé par défaut.
  4. le problème est qu'un développeur de bibliothèque ne devrait pas se soucier de savoir si ses clients ont besoin d'un flux de contexte ou non.
  5. [ajouté plus tard] le fait qu'il n'y ait pas de réponse faisant autorité et de référence appropriée et que nous continuions à nous battre à ce sujet signifie que quelqu'un n'a pas fait son travail correctement.

j'ai obtenu quelques postes sur le sujet, mais mon point de vue - en plus de la réponse agréable de Tugberk - est que vous devez tourner tous les APIs asynchrone et idéalement flux le contexte . puisque vous faites de l'async, vous pouvez simplement utiliser des continuations au lieu d'attendre de sorte qu'il n'y aura pas d'impasse car aucune attente n'est faite dans la bibliothèque et vous gardez le flux pour que le contexte soit préservé (comme HttpContext).

Le problème est quand une bibliothèque expose une API synchrone mais utilise une autre API asynchrone - donc vous devez utiliser Wait() / Result dans votre code.

7
répondu Aliostad 2017-09-07 16:25:01