Partager via


Utilisation de méthodes asynchrones dans ASP.NET 4.5

par Rick Anderson

Ce tutoriel vous apprend les bases de la création d’une application ASP.NET Web Forms asynchrone à l’aide de Visual Studio Express 2012 pour le web, qui est une version gratuite de Microsoft Visual Studio. Vous pouvez également utiliser Visual Studio 2012. Les sections suivantes sont incluses dans ce didacticiel.

Un exemple complet est fourni pour ce didacticiel à l’adresse
https://github.com/RickAndMSFT/Async-ASP.NET/ sur le site GitHub .

ASP.NET 4.5 Pages Web dans la combinaison de .NET 4.5 vous permet d’inscrire des méthodes asynchrones qui retournent un objet de type Task. Le .NET Framework 4 a introduit un concept de programmation asynchrone appelé Tâche et ASP.NET 4.5 prend en charge Task. Les tâches sont représentées par le type de tâche et les types associés dans l’espace de noms System.Threading.Tasks . Le .NET Framework 4.5 s’appuie sur cette prise en charge asynchrone avec les mots clés await et async qui rendent l’utilisation des objets Task beaucoup moins complexe que les approches asynchrones précédentes. Le mot clé await est un raccourci syntaxique qui indique qu’un morceau de code doit attendre de façon asynchrone sur un autre élément de code. Le mot clé asynchrone représente un indicateur que vous pouvez utiliser pour marquer des méthodes en tant que méthodes asynchrones basées sur des tâches. La combinaison de l’objet await, async et Task vous permet d’écrire beaucoup plus facilement du code asynchrone dans .NET 4.5. Le nouveau modèle pour les méthodes asynchrones est appelé modèle asynchrone basé sur les tâches (TAP). Ce tutoriel part du principe que vous êtes familiarisé avec la programmation asynchrone à l’aide de mots clés await et async et de l’espace de noms Task .

Pour plus d’informations sur l’utilisation des mots clés await et async et l’espace de noms Task , consultez les références suivantes.

Mode de traitement des requêtes par le pool de threads

Sur le serveur web, le .NET Framework gère un pool de threads utilisés pour traiter ASP.NET requêtes. À l'arrivée d'une requête, un thread du pool est distribué pour la traiter. Si la demande est traitée de manière synchrone, le thread qui traite la demande est occupé pendant le traitement de la demande, et ce thread ne peut pas traiter une autre requête.

Cela peut ne pas être un problème, car le pool de threads peut être suffisamment grand pour accueillir de nombreux threads occupés. Toutefois, le nombre de threads dans le pool de threads est limité (la valeur maximale par défaut pour .NET 4.5 est de 5 000). Dans les applications volumineuses avec une concurrence élevée des requêtes de longue durée, tous les threads disponibles peuvent être occupés. Cet état est appelé privation de thread. Lorsque cette condition est atteinte, le serveur web met en file d’attente les demandes. Si la file d’attente des demandes est saturée, le serveur web rejette les requêtes avec une status HTTP 503 (Serveur trop occupé). Le pool de threads CLR présente des limitations sur les nouvelles injections de threads. Si l’accès concurrentiel est bursty (c’est-à-dire que votre site web peut soudainement obtenir un grand nombre de demandes) et que tous les threads de requête disponibles sont occupés en raison d’appels back-end avec une latence élevée, le taux d’injection de threads limité peut faire en sorte que votre application réponde très mal. En outre, chaque nouveau thread ajouté au pool de threads a une surcharge (par exemple, 1 Mo de mémoire de pile). Une application web utilisant des méthodes synchrones pour traiter les appels à latence élevée où le pool de threads atteint le maximum par défaut .NET 4.5 de 5 000 threads consomme environ 5 Go de mémoire de plus qu’une application capable de traiter les mêmes requêtes à l’aide de méthodes asynchrones et de seulement 50 threads. Lorsque vous effectuez un travail asynchrone, vous n’utilisez pas toujours un thread. Par exemple, lorsque vous effectuez une demande de service web asynchrone, ASP.NET n’utilise pas de threads entre l’appel de méthode asynchrone et l’objet await. L’utilisation du pool de threads pour traiter les demandes avec une latence élevée peut entraîner un encombrement de mémoire important et une utilisation médiocre du matériel serveur.

Traitement des requêtes asynchrones

Dans les applications web qui voient un grand nombre de demandes simultanées au démarrage ou ont une charge en rafale (où l’accès concurrentiel augmente soudainement), le fait de rendre les appels de service web asynchrones augmente la réactivité de votre application. Une requête asynchrone demande la même durée de traitement qu'une requête synchrone. Par exemple, si une requête effectue un appel de service web qui nécessite deux secondes, la requête prend deux secondes, qu’elle soit effectuée de manière synchrone ou asynchrone. Toutefois, pendant un appel asynchrone, un thread n’est pas empêché de répondre à d’autres requêtes pendant qu’il attend la fin de la première requête. Par conséquent, les requêtes asynchrones empêchent la mise en file d’attente des requêtes et la croissance du pool de threads quand de nombreuses demandes simultanées appellent des opérations de longue durée.

Choix des méthodes synchrones ou asynchrones

Cette section répertorie les instructions relatives à l’utilisation des méthodes synchrones ou asynchrones. Ce ne sont que des directives; examinez chaque application individuellement pour déterminer si les méthodes asynchrones aident à améliorer les performances.

En général, utilisez des méthodes synchrones pour les conditions suivantes :

  • Les opérations sont simples ou à durée d'exécution courte.
  • La simplicité est plus importante que l'efficacité.
  • Les opérations sont essentiellement des opérations UC, et non des opérations qui impliquent une surcharge du disque ou du réseau. L’utilisation de méthodes asynchrones sur les opérations liées au processeur n’offre aucun avantage et entraîne davantage de surcharge.

En général, utilisez des méthodes asynchrones pour les conditions suivantes :

  • Vous appelez des services qui peuvent être consommés via des méthodes asynchrones et vous utilisez .NET 4.5 ou version ultérieure.

  • Les opérations utilisent le réseau ou les E/S de manière intensive et non le processeur.

  • Le parallélisme est plus important que la simplicité du code.

  • Vous souhaitez fournir un mécanisme qui permet aux utilisateurs d'annuler une requête à durée d'exécution longue.

  • Lorsque l’avantage du basculement de threads l’emporte sur le coût du commutateur de contexte. En général, vous devez rendre une méthode asynchrone si la méthode synchrone bloque le thread de requête ASP.NET sans effectuer de travail. En rendant l’appel asynchrone, le thread de demande de ASP.NET n’est pas bloqué pendant qu’il attend la fin de la demande de service web.

  • Les tests montrent que les opérations de blocage constituent un goulot d’étranglement dans les performances du site et qu’IIS peut traiter davantage de demandes à l’aide de méthodes asynchrones pour ces appels bloquants.

    L’exemple téléchargeable montre comment utiliser efficacement des méthodes asynchrones. L’exemple fourni a été conçu pour fournir une démonstration simple de la programmation asynchrone dans ASP.NET 4.5. L’exemple n’est pas destiné à être une architecture de référence pour la programmation asynchrone dans ASP.NET. L’exemple de programme appelle API Web ASP.NET méthodes qui, à leur tour, appellent Task.Delay pour simuler des appels de service web de longue durée. La plupart des applications de production ne présentent pas de tels avantages évidents à l’utilisation de méthodes asynchrones.

Peu d’applications nécessitent que toutes les méthodes soient asynchrones. Souvent, la conversion de quelques méthodes synchrones en méthodes asynchrones offre la meilleure augmentation de l’efficacité pour la quantité de travail requise.

Exemple d’application

Vous pouvez télécharger l’exemple d’application à partir du https://github.com/RickAndMSFT/Async-ASP.NET site GitHub . Le dépôt se compose de trois projets :

  • WebAppAsync : projet ASP.NET Web Forms qui utilise le service WebAPIpwg de l’API web. La majeure partie du code de ce didacticiel provient de ce projet.
  • WebAPIpgw : ASP.NET projet d’API web MVC 4 qui implémente les Products, Gizmos and Widgets contrôleurs. Il fournit les données du projet WebAppAsync et du projet Mvc4Async .
  • Mvc4Async : ASP.NET projet MVC 4 qui contient le code utilisé dans un autre didacticiel. Il effectue des appels d’API web au service WebAPIpwg .

Page synchrone Gizmos

Le code suivant montre la Page_Load méthode synchrone utilisée pour afficher une liste de gizmos. (Pour cet article, un gizmo est un dispositif mécanique fictif.)

public partial class Gizmos : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        var gizmoService = new GizmoService();
        GizmoGridView.DataSource = gizmoService.GetGizmos();
        GizmoGridView.DataBind();
    }
}

Le code suivant montre la GetGizmos méthode du service gizmo.

public class GizmoService
{
    public async Task<List<Gizmo>> GetGizmosAsync(
        // Implementation removed.
       
    public List<Gizmo> GetGizmos()
    {
        var uri = Util.getServiceUri("Gizmos");
        using (WebClient webClient = new WebClient())
        {
            return JsonConvert.DeserializeObject<List<Gizmo>>(
                webClient.DownloadString(uri)
            );
        }
    }
}

La GizmoService GetGizmos méthode transmet un URI à un service HTTP API Web ASP.NET qui retourne une liste de données gizmos. Le projet WebAPIpgw contient l’implémentation de l’API gizmos, widget web et product des contrôleurs.
L’image suivante montre la page gizmos de l’exemple de projet.

Capture d’écran de la page du navigateur web Sync Gizmos montrant la table des gizmos avec les détails correspondants entrés dans les contrôleurs d’API web.

Création d’une page gizmos asynchrone

L’exemple utilise les nouveaux mots clés asynchrones et await (disponibles dans .NET 4.5 et Visual Studio 2012) pour permettre au compilateur de gérer les transformations complexes nécessaires à la programmation asynchrone. Le compilateur vous permet d’écrire du code à l’aide des constructions de flux de contrôle synchrones du C#, et le compilateur applique automatiquement les transformations nécessaires pour utiliser les rappels afin d’éviter de bloquer les threads.

ASP.NET pages asynchrones doivent inclure la directive Page avec l’attribut Async défini sur « true ». Le code suivant montre la directive Page avec l’attribut Async défini sur « true » pour la page GizmosAsync.aspx .

<%@ Page Async="true"  Language="C#" AutoEventWireup="true" 
    CodeBehind="GizmosAsync.aspx.cs" Inherits="WebAppAsync.GizmosAsync" %>

Le code suivant montre la Gizmos méthode synchrone Page_Load et la GizmosAsync page asynchrone. Si votre navigateur prend en charge l’élément de marque> HTML 5<, vous verrez les modifications en GizmosAsync surbrillance jaune.

protected void Page_Load(object sender, EventArgs e)
{
   var gizmoService = new GizmoService();
   GizmoGridView.DataSource = gizmoService.GetGizmos();
   GizmoGridView.DataBind();
}

Version asynchrone :

protected void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcAsync));
}

private async Task GetGizmosSvcAsync()
{
    var gizmoService = new GizmoService();
    GizmosGridView.DataSource = await gizmoService.GetGizmosAsync();
    GizmosGridView.DataBind();
}

Les modifications suivantes ont été appliquées pour permettre à la GizmosAsync page d’être asynchrone.

  • L’attribut de la directive Page doit avoir la Async valeur « true ».
  • La RegisterAsyncTask méthode est utilisée pour inscrire une tâche asynchrone contenant le code qui s’exécute de manière asynchrone.
  • La nouvelle GetGizmosSvcAsync méthode est marquée avec le mot clé asynchrone, qui indique au compilateur de générer des rappels pour certaines parties du corps et de créer automatiquement un Task qui est retourné.
  • « Async » a été ajouté au nom de la méthode asynchrone. L’ajout de « Async » n’est pas obligatoire, mais constitue la convention lors de l’écriture de méthodes asynchrones.
  • Le type de retour de la nouvelle GetGizmosSvcAsync méthode est Task. Le type de retour de Task représente le travail en cours et fournit aux appelants de la méthode un handle à travers lequel attendre la fin de l’opération asynchrone.
  • Le mot clé d’attente a été appliqué à l’appel de service web.
  • L’API de service web asynchrone a été appelée (GetGizmosAsync).

À l’intérieur du corps de la GetGizmosSvcAsync méthode, GetGizmosAsync une autre méthode asynchrone est appelée. GetGizmosAsync retourne immédiatement un Task<List<Gizmo>> qui finira par se terminer lorsque les données seront disponibles. Étant donné que vous ne souhaitez rien faire d’autre tant que vous n’avez pas les données gizmo, le code attend la tâche (à l’aide du mot clé d’attente). Vous pouvez utiliser le mot clé await uniquement dans les méthodes annotées avec le mot clé asynchrone.

Le mot clé await ne bloque pas le thread tant que la tâche n’est pas terminée. Il inscrit le reste de la méthode en tant que rappel sur la tâche et retourne immédiatement. Une fois la tâche attendue terminée, elle appelle ce rappel et reprend donc l’exécution de la méthode là où elle s’est arrêté. Pour plus d’informations sur l’utilisation des mots clés await et async et de l’espace de noms Task , consultez les références asynchrones.

Le code suivant représente les méthodes GetGizmos et GetGizmosAsync.

public List<Gizmo> GetGizmos()
{
    var uri = Util.getServiceUri("Gizmos");
    using (WebClient webClient = new WebClient())
    {
        return JsonConvert.DeserializeObject<List<Gizmo>>(
            webClient.DownloadString(uri)
        );
    }
}
public async Task<List<Gizmo>> GetGizmosAsync()
{
    var uri = Util.getServiceUri("Gizmos");
    using (WebClient webClient = new WebClient())
    {
        return JsonConvert.DeserializeObject<List<Gizmo>>(
            await webClient.DownloadStringTaskAsync(uri)
        );
    }
}

Les modifications asynchrones sont similaires à celles apportées à GizmosAsync ci-dessus.

  • La signature de méthode a été annotée avec le mot clé asynchrone, le type de retour a été remplacé par Task<List<Gizmo>>et Async a été ajouté au nom de la méthode.
  • La classe HttpClient asynchrone est utilisée à la place de la classe WebClient synchrone.
  • Le mot clé await a été appliqué à la méthode asynchrone HttpClientGetAsync.

L’image suivante montre la vue gizmo asynchrone.

Capture d’écran de la page du navigateur web Gizmos Async montrant la table des gizmos avec les détails correspondants entrés dans les contrôleurs d’API web.

La présentation dans les navigateurs des données de gizmos est identique à la vue créée par l’appel synchrone. La seule différence est que la version asynchrone peut être plus performante en cas de charges lourdes.

RegisterAsyncTask Notes

Les méthodes branchées avec RegisterAsyncTask s’exécutent immédiatement après PreRender.

Si vous utilisez directement des événements de page async void, comme indiqué dans le code suivant :

protected async void Page_Load(object sender, EventArgs e) {
    await ...;
    // do work
}

vous n’avez plus de contrôle total sur le moment où les événements s’exécutent. Par exemple, si un .aspx et un . Les événements de définition Page_Load maître et l’un d’eux ou les deux sont asynchrones. L’ordre d’exécution ne peut pas être garanti. Le même ordre indéterminé pour les gestionnaires d’événements (tels que async void Button_Click ) s’applique.

Exécution de plusieurs opérations en parallèle

Les méthodes asynchrones présentent un avantage significatif par rapport aux méthodes synchrones lorsqu’une action doit effectuer plusieurs opérations indépendantes. Dans l’exemple fourni, la page synchrone PWG.aspx(for Products, Widgets and Gizmos) affiche les résultats de trois appels de service web pour obtenir une liste de produits, widgets et gizmos. Le projet API Web ASP.NET qui fournit ces services utilise Task.Delay pour simuler la latence ou les appels réseau lents. Lorsque le délai est défini sur 500 millisecondes, la page asynchrone PWGasync.aspx prend un peu plus de 500 millisecondes, tandis que la version synchrone PWG prend plus de 1 500 millisecondes. La page PWG.aspx synchrone s’affiche dans le code suivant.

protected void Page_Load(object sender, EventArgs e)
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var pwgVM = new ProdGizWidgetVM(
        widgetService.GetWidgets(),
        prodService.GetProducts(),
        gizmoService.GetGizmos()
       );
    WidgetGridView.DataSource = pwgVM.widgetList;
    WidgetGridView.DataBind();
    ProductGridView.DataSource = pwgVM.prodList;
    ProductGridView.DataBind();
    GizmoGridView.DataSource = pwgVM.gizmoList;
    GizmoGridView.DataBind();

    stopWatch.Stop();
    ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}", 
        stopWatch.Elapsed.Milliseconds / 1000.0);
}

Le code asynchrone PWGasync derrière est illustré ci-dessous.

protected void Page_Load(object sender, EventArgs e)
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
    RegisterAsyncTask(new PageAsyncTask(GetPWGsrvAsync));
    stopWatch.Stop();
    ElapsedTimeLabel.Text = String.Format("Elapsed time: {0}",
        stopWatch.Elapsed.Milliseconds / 1000.0);
}

private async Task GetPWGsrvAsync()
{
    var widgetService = new WidgetService();
    var prodService = new ProductService();
    var gizmoService = new GizmoService();

    var widgetTask = widgetService.GetWidgetsAsync();
    var prodTask = prodService.GetProductsAsync();
    var gizmoTask = gizmoService.GetGizmosAsync();

    await Task.WhenAll(widgetTask, prodTask, gizmoTask);

    var pwgVM = new ProdGizWidgetVM(
       widgetTask.Result,
       prodTask.Result,
       gizmoTask.Result
       );

    WidgetGridView.DataSource = pwgVM.widgetList;
    WidgetGridView.DataBind();
    ProductGridView.DataSource = pwgVM.prodList;
    ProductGridView.DataBind();
    GizmoGridView.DataSource = pwgVM.gizmoList;
    GizmoGridView.DataBind();           
}

L’image suivante montre l’affichage retourné à partir de la page PWGasync.aspx asynchrone.

Capture d’écran de la page de navigateur web Widgets, Produits et Gizmos asynchrones montrant les tables Widgets, Produits et Gizmos.

Utilisation d’un jeton d’annulation

Les méthodes asynchrones retournées Tasksont annulables, c’est-à-dire qu’elles prennent un paramètre CancelToken quand l’attribut de la directive Page est fourniAsyncTimeout. Le code suivant montre la page GizmosCancelAsync.aspx avec un délai d’expiration à la seconde.

<%@ Page  Async="true"  AsyncTimeout="1" 
    Language="C#" AutoEventWireup="true" 
    CodeBehind="GizmosCancelAsync.aspx.cs" 
    Inherits="WebAppAsync.GizmosCancelAsync" %>

Le code suivant montre le fichier GizmosCancelAsync.aspx.cs .

protected void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(GetGizmosSvcCancelAsync));
}

private async Task GetGizmosSvcCancelAsync(CancellationToken cancellationToken)
{
    var gizmoService = new GizmoService();
    var gizmoList = await gizmoService.GetGizmosAsync(cancellationToken);
    GizmosGridView.DataSource = gizmoList;
    GizmosGridView.DataBind();
}
private void Page_Error(object sender, EventArgs e)
{
    Exception exc = Server.GetLastError();

    if (exc is TimeoutException)
    {
        // Pass the error on to the Timeout Error page
        Server.Transfer("TimeoutErrorPage.aspx", true);
    }
}

Dans l’exemple d’application fourni, la sélection du lien GizmosCancelAsync appelle la page GizmosCancelAsync.aspx et illustre l’annulation (par délai d’expiration) de l’appel asynchrone. Étant donné que le délai se situe dans une plage aléatoire, vous devrez peut-être actualiser la page plusieurs fois pour obtenir le message d’erreur de délai d’expiration.

Configuration du serveur pour les appels de service web à haute concurrence/latence élevée

Pour tirer parti des avantages d’une application web asynchrone, vous devrez peut-être apporter des modifications à la configuration du serveur par défaut. Gardez à l’esprit ce qui suit lors de la configuration et du test de contrainte de votre application web asynchrone.

  • Windows 7, Windows Vista, Windows 8 et tous les systèmes d’exploitation clients Windows ont un maximum de 10 demandes simultanées. Vous aurez besoin d’un système d’exploitation Windows Server pour voir les avantages des méthodes asynchrones sous une charge élevée.

  • Inscrivez .NET 4.5 auprès d’IIS à partir d’une invite de commandes avec élévation de privilèges à l’aide de la commande suivante :
    %windir%\Microsoft.NET\Framework64 \v4.0.30319\aspnet_regiis -i
    Consultez ASP.NET’outil d’inscription IIS (Aspnet_regiis.exe)

  • Vous devrez peut-être augmenter la limite de file d’attenteHTTP.sysde la valeur par défaut de 1 000 à 5 000. Si le paramètre est trop faible, vous pouvez voir HTTP.sys refuser des demandes avec une status HTTP 503. Pour modifier la limite de file d’attente HTTP.sys :

    • Ouvrez le Gestionnaire IIS et accédez au volet Pools d’applications.
    • Cliquez avec le bouton droit sur le pool d’applications cible et sélectionnez Paramètres avancés.
      Capture d’écran du Gestionnaire des services Internet Information montrant le menu Paramètres avancés mis en évidence avec un rectangle rouge.
    • Dans la boîte de dialogue Paramètres avancés , définissez la longueur de la file d’attente de 1 000 à 5 000.
      Capture d’écran de la boîte de dialogue Paramètres avancés montrant le champ Longueur de la file d’attente défini sur 1000 et mis en surbrillance avec un rectangle rouge.

    Notez que dans les images ci-dessus, le .NET Framework est répertorié comme v4.0, même si le pool d’applications utilise .NET 4.5. Pour comprendre cette différence, consultez les éléments suivants :

  • Contrôle de version et multi-ciblage .NET : .NET 4.5 est une mise à niveau sur place vers .NET 4.0

  • Comment définir une application IIS ou Un pool d’applications pour utiliser ASP.NET 3.5 plutôt que 2.0

  • Versions et dépendances de .NET Framework

  • Si votre application utilise des services web ou des System.NET pour communiquer avec un back-end via HTTP, vous devrez peut-être augmenter l’élément connectionManagement/maxconnection . Pour ASP.NET applications, la fonctionnalité de configuration automatique est limitée à 12 fois le nombre de processeurs. Cela signifie que sur un quad-proc, vous pouvez avoir au maximum 12 * 4 = 48 connexions simultanées à un point de terminaison IP. Étant donné que cela est lié à la configuration automatique, le moyen le plus simple d’augmenter maxconnection dans une application ASP.NET consiste à définir System.Net.ServicePointManager.DefaultConnectionLimit par programmation dans la méthode from Application_Start dans le fichier global.asax . Consultez l’exemple de téléchargement pour obtenir un exemple.

  • Dans .NET 4.5, la valeur par défaut 5000 pour MaxConcurrentRequestsPerCPU doit être correcte.

Contributeurs