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.
- Mode de traitement des requêtes par le pool de threads
- Choix des méthodes synchrones ou asynchrones
- Exemple d’application
- Page synchrone Gizmos
- Création d’une page gizmos asynchrone
- Exécution de plusieurs opérations en parallèle
- Utilisation d’un jeton d’annulation
- Configuration du serveur pour les appels de service web à haute concurrence/latence élevée
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.
- Livre blanc : Asynchronie dans .NET
- Questions fréquentes (FAQ) sur Async/Await
- Programmation asynchrone Visual Basic
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.
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 unTask
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 estTask
. Le type de retour deTask
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.
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.
Utilisation d’un jeton d’annulation
Les méthodes asynchrones retournées Task
sont 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.
- Dans la boîte de dialogue Paramètres avancés , définissez la longueur de la file d’attente de 1 000 à 5 000.
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
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 fromApplication_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.