web-dev-qa-db-fra.com

Il est recommandé d'appeler ConfigureAwait pour tous les codes côté serveur.

Lorsque vous avez un code côté serveur (c'est-à-dire que ApiController) et que vos fonctions sont asynchrones - de sorte qu'elles renvoient Task<SomeObject> - la bonne pratique est-elle de toujours privilégier les fonctions que vous appelez ConfigureAwait(false) ?

J'avais lu qu'il est plus performant puisqu'il n'a pas à redéfinir les contextes de thread dans le contexte de thread d'origine. Cependant, avec ASP.NET Web Api, si votre demande arrive sur un seul thread et que vous attendez une fonction, appelez ConfigureAwait(false) et risquez de vous placer sur un autre thread lorsque vous renvoyez le résultat final de votre ApiController fonction.

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;
    }
}
487
Arash Emami

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

Pour ASP.NET "Complet" ou "Classique" ou autre chose, le reste de cette réponse s'applique toujours.

Message original (pour ASP.NET non principal):

Cette vidéo de l'équipe ASP.NET contient les meilleures informations sur l'utilisation de async sur ASP.NET.

J'avais lu qu'il est plus performant car il n'est pas nécessaire de remettre les contextes de thread dans le contexte de thread d'origine.

Cela est vrai avec les applications d'interface utilisateur, où il n'y a qu'un seul thread sur lequel vous devez "synchroniser".

Dans ASP.NET, la situation est un peu plus complexe. Lorsqu'une méthode async reprend l'exécution, elle récupère un thread du pool de threads ASP.NET. Si vous désactivez la capture de contexte à l'aide de ConfigureAwait(false), le thread continue simplement d'exécuter la méthode directement. Si vous ne désactivez pas la capture de contexte, le thread entrera de nouveau dans le contexte de la demande, puis continuera à exécuter la méthode.

Donc, ConfigureAwait(false) ne vous enregistre pas un saut de fil dans ASP.NET; cela vous évite de rentrer dans le contexte de la requête, mais c'est normalement très rapide. ConfigureAwait(false) pourrait être utile si vous essayez d'effectuer un petit nombre de traitements en parallèle d'une requête, mais que TPL convient mieux à la plupart de ces scénarios.

Cependant, avec ASP.NET Web Api, si votre demande arrive sur un seul thread et que vous attendez une fonction, appelez ConfigureAwait (false) qui pourrait vous placer sur un autre thread lorsque vous renvoyez le résultat final de votre fonction ApiController. .

En fait, il suffit de faire un await pour le faire. Une fois que votre méthode async a atteint un await, la méthode est bloquée mais le thread retourne à la pool de threads. Lorsque la méthode est prête à continuer, tout thread est arraché du pool de threads et utilisé pour reprendre la méthode.

La seule différence que ConfigureAwait fait dans ASP.NET est de savoir si ce thread entre dans le contexte de la demande lors de la reprise de la méthode.

J'ai plus d'informations de base dans mon article MSDN sur SynchronizationContext et my async post sur le blog .

544
Stephen Cleary

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

TL; DR version de la réponse longue: 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 utilisateurs de votre bibliothèque risquent de faire face à des blocages en consommant vos méthodes asynchrones de manière bloquante. Cela 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):

Lorsque vous attendez une méthode avec un mot-clé wait, le compilateur génère un tas de code en votre nom. L'un des objectifs de cette action est de gérer la synchronisation avec le thread d'interface utilisateur (ou principal). Le composant clé de cette fonctionnalité est le SynchronizationContext.Current qui obtient le contexte de synchronisation du thread en cours. SynchronizationContext.Current est rempli en fonction de l'environnement dans lequel vous vous trouvez. La méthode GetAwaiter de tâche recherche SynchronizationContext.Current. Si le contexte de synchronisation actuel n'est pas nul, la continuation transmise à cet attente sera publiée dans ce contexte de synchronisation.

Lorsque vous utilisez une méthode, qui utilise les nouvelles fonctionnalités du langage asynchrone, de manière bloquante, vous obtiendrez un interblocage si vous avez un SynchronizationContext disponible. Lorsque vous utilisez ces méthodes de manière bloquante (en attente de la méthode Tâche avec attente ou en prenant le résultat directement à partir de la propriété Result de la tâche), vous bloquez simultanément le thread principal. Lorsque finalement la tâche se termine à l'intérieur de cette méthode dans le pool de threads, elle appelle la suite pour la publier dans le thread principal car SynchronizationContext.Current est disponible et capturé. Mais il y a un problème ici: le fil de l'interface utilisateur est bloqué et vous êtes dans une impasse!

En outre, voici deux excellents articles pour vous qui sont exactement pour votre question:

Enfin, il existe une excellente courte vidéo de Lucian Wischik sur ce sujet: Les méthodes de bibliothèque asynchrone doivent envisager d’utiliser Task.ConfigureAwait (false) .

J'espère que cela t'aides.

123
tugberk

Le plus gros inconvénient que j'ai constaté lors de l'utilisation de ConfigureAwait (false) est que la culture de threads est revenue à la valeur par défaut du système. Si vous avez configuré une culture, par exemple ...

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

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

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

Si votre application exécute une tâche nécessitant un formatage de données spécifique à la culture, vous devez en être conscient lorsque vous utilisez ConfigureAwait (false).

16
Mick

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

  1. La tâche est disponible mais nous sommes pas censés le faire utiliser using.
  2. ConfigureAwait a été introduit en 4.5. Task a été introduit dans la version 4.0.
  3. .NET Threads toujours utilisé pour définir le contexte (voir C # via le livre CLR), mais dans l'implémentation par défaut de Task.ContinueWith, ils ne sont pas b/c, mais le changement de contexte a été réalisé. 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, par conséquent, il ne devrait pas décider si le flux de texte doit être transféré 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 continuons à nous battre sur ce point signifie que quelqu'un n'a pas bien fait son travail.

J'ai quelques posts sur le sujet, mais ma conclusion - en plus de la bonne réponse de Tugberk - est que vous devez activer toutes les API de manière asynchrone et, dans l'idéal, gérer le contexte. Puisque vous effectuez une opération asynchrone, vous pouvez simplement utiliser des continuations au lieu d’attendre pour éviter tout blocage, car aucune attente n’est faite dans la bibliothèque et vous conservez le flux continu afin que le contexte soit préservé (comme HttpContext).

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

10
Aliostad