Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Rick Anderson
Este tutorial vai ensinar-lhe o básico para construir uma aplicação assíncrona ASP.NET Web Forms usando o Visual Studio Express 2012 para Web, que é uma versão gratuita do Microsoft Visual Studio. Também pode usar o Visual Studio 2012. As secções seguintes estão incluídas neste tutorial.
- Como os pedidos são processados pelo pool de threads
- Escolha de Métodos Síncronos ou Assíncronos
- A Aplicação de Exemplo
- A Página Síncrona dos Gizmos
- Criação de uma Página de Gizmos Assíncronos
- Realização de Múltiplas Operações em Paralelo
- Utilização de um Token de Cancelamento
- Configuração do Servidor para Chamadas de Serviço Web de Alta Concorrência/Alta Latência
Um exemplo completo é fornecido para este tutorial em
https://github.com/RickAndMSFT/Async-ASP.NET/ no site do GitHub .
ASP.NET 4.5 Web Pages em combinação .NET 4.5 permite-lhe registar métodos assíncronos que retornam um objeto do tipo Task. O .NET Framework 4 introduziu um conceito de programação assíncrona referido como Tarefa e ASP.NET 4.5 suporta Tarefa. As tarefas são representadas pelo tipo Tarefa e tipos relacionados no namespace System.Threading.Tasks. O .NET Framework 4.5 baseia-se neste suporte assíncrono com as palavras-chave await e assync , que tornam o trabalho com objetos Task muito menos complexo do que as abordagens assíncronas anteriores. A palavra-chave await é uma abreviatura sintática para indicar que um pedaço de código deve esperar assíncronamente por outro pedaço de código. A palavra-chave assíncrona representa uma dica que pode usar para marcar métodos como métodos assíncronos baseados em tarefas. A combinação de await, async e o objeto Task torna muito mais fácil escrever código assíncrono em .NET 4.5. O novo modelo para métodos assíncronos chama-se Padrão Assíncrono Baseado em Tarefas (TAP). Este tutorial assume que tens alguma familiaridade com programação assíncrona usando palavras-chave await e assíncronas e o namespace Tarefa .
Para mais informações sobre o uso das palavras-chave await e async e do espaço de nomes Task, consulte as seguintes referências.
Como os pedidos são processados pelo "thread pool"
No servidor web, o .NET Framework mantém um conjunto de threads usados para servir pedidos ASP.NET. Quando chega um pedido, um thread do pool é enviado para processar esse pedido. Se o pedido for processado de forma síncrona, a thread que processa o pedido está ocupada enquanto o pedido está a ser processado, e essa thread não pode servir outro pedido.
Isto pode não ser um problema, porque o pool de threads pode ser grande o suficiente para acomodar muitos threads ocupados. No entanto, o número de threads no pool de threads é limitado (o máximo padrão para .NET 4.5 é 5.000). Em aplicações grandes com alta concorrência em pedidos de longa duração, todas as threads disponíveis podem estar ocupadas. Esta condição é conhecida como inanição de fios. Quando esta condição é alcançada, o servidor web coloca os pedidos em fila. Se a fila de pedidos ficar cheia, o servidor web rejeita pedidos com um estado HTTP 503 (Servidor Demasiado Ocupado). O pool de threads CLR tem limitações na injeção de novas threads. Se a concorrência for súbita (ou seja, o seu site pode de repente receber um grande número de pedidos) e todos os threads de pedidos disponíveis estiverem ocupados devido a chamadas backend com alta latência, a taxa limitada de inserção de threads pode fazer com que a sua aplicação responda muito mal. Além disso, cada nova thread adicionada ao pool de threads gera uma sobrecarga (como 1 MB de memória de pilha). Uma aplicação web usando métodos síncronos para servir chamadas de alta latência, onde o pool de threads cresce até ao máximo padrão .NET 4.5 de 5.000 threads, consumiria aproximadamente 5 GB a mais de memória do que uma aplicação capaz de servir os mesmos pedidos usando métodos assíncronos e apenas 50 threads. Quando fazes trabalho assíncrono, nem sempre estás a usar uma thread. Por exemplo, quando faz um pedido de serviço web assíncrono, ASP.NET não estará a usar nenhum thread entre a chamada de método assíncrona e o await. Usar o pool de threads para atender pedidos com alta latência pode levar a um grande consumo de memória e a uma má utilização do hardware do servidor.
Processamento de Pedidos Assíncronos
Em aplicações web que recebem um grande número de pedidos simultâneos no arranque ou apresentam um bursty load (onde a concorrência aumenta subitamente), tornar as chamadas de serviço web assíncronas aumentará a capacidade de resposta da sua aplicação. Um pedido assíncrono demora o mesmo tempo a ser processado que um pedido síncrono. Por exemplo, se um pedido fizer uma chamada de serviço web que requer dois segundos a ser concluída, o pedido demora dois segundos, quer seja realizado de forma síncrona ou assíncrona. No entanto, durante uma chamada assíncrona, um thread não é impedido de responder a outros pedidos enquanto espera que o primeiro pedido esteja concluído. Assim, os pedidos assíncronos impedem a formação de filas de pedidos e o crescimento do pool de threads quando existem muitos pedidos concorrentes que provocam operações de longa duração.
Escolha de Métodos Síncronos ou Assíncronos
Esta secção lista orientações sobre quando usar Métodos síncronos ou assíncronos. Estas são apenas orientações; Analise cada aplicação individualmente para determinar se os métodos assíncronos ajudam no desempenho.
Em geral, utilize-se métodos síncronos para as seguintes condições:
- As operações são simples ou de curta duração.
- A simplicidade é mais importante do que a eficiência.
- As operações são principalmente operações de CPU, em vez de operações que envolvam uma sobrecarga extensa em disco ou rede. Usar métodos assíncronos em operações limitadas à CPU não traz benefícios e resulta em maior sobrecarga.
Em geral, utilize-se métodos assíncronos para as seguintes condições:
Estás a chamar serviços que podem ser consumidos por métodos assíncronos, e estás a usar .NET 4.5 ou superior.
As operações são ligadas à rede ou I/O em vez de dependentes da CPU.
O paralelismo é mais importante do que a simplicidade do código.
Quer fornecer um mecanismo que permita aos utilizadores cancelar um pedido de longa duração.
Quando o benefício de mudar de thread supera o custo da troca de contexto. Em geral, deves tornar um método assíncrono se o método síncrono bloquear a thread de pedido ASP.NET enquanto não faz trabalho algum. Ao tornar a chamada assíncrona, o thread de requisição do ASP.NET não fica bloqueado, sem realizar trabalho nenhum, enquanto aguarda a conclusão do pedido do serviço web.
Os testes mostram que as operações de bloqueio são um gargalo no desempenho do site e que o IIS pode processar mais pedidos utilizando métodos assíncronos para estas chamadas de bloqueio.
O exemplo descarregável mostra como usar métodos assíncronos de forma eficaz. O exemplo fornecido foi concebido para proporcionar uma demonstração simples de programação assíncrona em ASP.NET 4.5. O exemplo não se destina a ser uma arquitetura de referência para programação assíncrona em ASP.NET. O programa de exemplo chama métodos da API Web ASP.NET, que por sua vez invocam Task.Delay para simular chamadas de serviço web prolongadas. A maioria das aplicações de produção não apresenta benefícios tão óbvios na utilização de Métodos assíncronos.
Poucas aplicações exigem que todos os métodos sejam assíncronos. Muitas vezes, converter alguns métodos síncronos em métodos assíncronos proporciona o melhor aumento de eficiência para a quantidade de trabalho necessária.
A Aplicação de Exemplo
Podes descarregar a aplicação de exemplo no https://github.com/RickAndMSFT/Async-ASP.NET site do GitHub . O repositório é composto por três projetos:
- WebAppAsync: O projeto ASP.NET Web Forms que consome o serviço Web API WebAPIpwg . A maior parte do código deste tutorial vem deste projeto.
-
WebAPIpgw: O projeto ASP.NET MVC 4 Web API que implementa os
Products, Gizmos and Widgetscontroladores. Fornece os dados para o projeto WebAppAsync e para o projeto Mvc4Async . - Mvc4Async: O projeto ASP.NET MVC 4 que contém o código usado noutro tutorial. Faz chamadas Web API para o serviço WebAPIpwg .
A Página Síncrona dos Gizmos
O código seguinte mostra o Page_Load método síncrono usado para mostrar uma lista de gizmos. (Para este artigo, um aparelho é um dispositivo mecânico fictício.)
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();
}
}
O código seguinte mostra o GetGizmos método do serviço 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)
);
}
}
}
O GizmoService GetGizmos método passa um URI para um serviço HTTP da ASP.NET Web API, que devolve uma lista de dados de gizmos. O projeto WebAPIpgw contém a implementação da Web API gizmos, widget e product controladores.
A imagem seguinte mostra a página dos gadgets do projeto de exemplo.
Criação de uma Página de Gizmos Assíncronos
O exemplo utiliza as novas palavras-chave async e await (disponíveis em .NET 4.5 e Visual Studio 2012) para permitir que o compilador seja responsável por manter as transformações complexas necessárias para programação assíncrona. O compilador permite-lhe escrever código usando as construções de fluxo de controlo síncrono do C# e o compilador aplica automaticamente as transformações necessárias para usar callbacks, de modo a evitar bloquear threads.
ASP.NET páginas assíncronas devem incluir a diretiva Page com o Async atributo definido como "true". O código seguinte mostra a diretiva Página com o Async atributo definido como "verdadeiro" para a página GizmosAsync.aspx .
<%@ Page Async="true" Language="C#" AutoEventWireup="true"
CodeBehind="GizmosAsync.aspx.cs" Inherits="WebAppAsync.GizmosAsync" %>
O código seguinte mostra o Gizmos método síncrono Page_Load e a GizmosAsync página assíncrona. Se o seu navegador suportar o elemento HTML5 <mark>, verá as alterações destacadas em GizmosAsync amarelo.
protected void Page_Load(object sender, EventArgs e)
{
var gizmoService = new GizmoService();
GizmoGridView.DataSource = gizmoService.GetGizmos();
GizmoGridView.DataBind();
}
A versão assíncrona:
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();
}
As alterações seguintes foram aplicadas para permitir que a GizmosAsync página fosse assíncrona.
- A diretiva Page deve ter o
Asyncatributo definido como "true". - O
RegisterAsyncTaskmétodo é usado para registar uma tarefa assíncrona contendo o código que corre de forma assíncrona. - O novo
GetGizmosSvcAsyncmétodo está marcado com a palavra-chave async, que indica ao compilador que deve gerar callbacks para partes do corpo do método e criar automaticamente umTaskque é retornado. - "Async" foi acrescentado ao nome do método assíncrono. Adicionar "Assíncrono" não é obrigatório, mas é a convenção ao escrever métodos assíncronos.
- O tipo de retorno do novo
GetGizmosSvcAsyncmétodo éTask. O tipo de retorno deTaskrepresenta o trabalho em andamento e fornece aos chamadores do método uma referência através da qual podem aguardar a conclusão da operação assíncrona. - A palavra-chave await foi aplicada à chamada do serviço web.
- A API de serviço web assíncrona chamava-se (
GetGizmosAsync).
Dentro do corpo do GetGizmosSvcAsync método, outro método GetGizmosAsync assíncrono é chamado.
GetGizmosAsync retorna imediatamente um Task<List<Gizmo>> que acabará por ser concluído quando os dados estiverem disponíveis. Como não queres fazer mais nada até teres os dados do gizmo, o código aguarda a tarefa (usando a palavra-chave await ). Pode usar a palavra-chave await apenas em métodos anotados com a palavra-chave assíncrona .
A palavra-chave await não bloqueia a thread até que a tarefa esteja concluída. Regista o resto do método como um callback da tarefa e regressa imediatamente. Quando a tarefa aguardada finalmente termina, invoca esse callback e assim retoma a execução do método exatamente onde ficou. Para mais informações sobre o uso das palavras-chave await e assync e do namespace Task , consulte as referências assíncronas.
O código seguinte mostra os GetGizmos métodos e 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)
);
}
}
As alterações assíncronas são semelhantes às feitas ao GizmosAsync acima.
- A assinatura do método foi anotada com a palavra-chave assíncrona , o tipo de retorno foi alterado para
Task<List<Gizmo>>, e Assíncrono foi adicionado ao nome do método. - A classe HttpClient assíncrona é usada em vez da classe WebClient síncrona.
- A palavra-chave await foi aplicada ao método assíncrono GetAsync do HttpClient.
A imagem seguinte mostra a visualização assíncrona do gizmo.
A apresentação dos dados dos gizmos pelo navegador é idêntica à vista criada pela chamada síncrona. A única diferença é que a versão assíncrona pode ser mais eficiente sob cargas pesadas.
Notas do RegisterAsyncTask
Os métodos ligados com RegisterAsyncTask serão executados imediatamente após o PreRender.
Se usar eventos de página void assíncronos diretamente, como mostrado no seguinte código:
protected async void Page_Load(object sender, EventArgs e) {
await ...;
// do work
}
Já não tens controlo total sobre quando os eventos se executam. Por exemplo, se tanto um .aspx quanto um .Master definem eventos Page_Load e um ou ambos são assíncronos, a ordem de execução não pode ser garantida. Aplica-se a mesma ordem indeterminada para os gestores de eventos (como async void Button_Click ).
Realização de Múltiplas Operações em Paralelo
Os Métodos Assíncronos têm uma vantagem significativa em relação aos métodos síncronos quando uma ação tem de realizar várias operações independentes. No exemplo fornecido, a página síncrona PWG.aspx (para Produtos, Widgets e Gizmos) mostra os resultados de três chamadas de serviço web para obter uma lista de produtos, widgets e gadgets. O projeto ASP.NET Web API que fornece estes serviços utiliza o Task.Delay para simular latência ou chamadas de rede lentas. Quando o atraso é definido para 500 milissegundos, a página de PWGasync.aspx assíncrona demora pouco mais de 500 milissegundos a completar, enquanto a versão síncrona PWG demora mais de 1.500 milissegundos. A página de PWG.aspx síncrona é mostrada no seguinte código.
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);
}
O código assíncrono PWGasync por trás é mostrado abaixo.
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();
}
A imagem seguinte mostra a vista devolvida da página assíncrona PWGasync.aspx.
Utilização de um Token de Cancelamento
Os Métodos Assíncronos que retornam Tasksão canceláveis, ou seja, recebem um parâmetro CancellationToken quando lhes é fornecido o AsyncTimeout atributo da diretiva Page . O código seguinte mostra a página GizmosCancelAsync.aspx com um timeout de um segundo.
<%@ Page Async="true" AsyncTimeout="1"
Language="C#" AutoEventWireup="true"
CodeBehind="GizmosCancelAsync.aspx.cs"
Inherits="WebAppAsync.GizmosCancelAsync" %>
O código seguinte mostra o ficheiro 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);
}
}
Na aplicação de exemplo fornecida, ao selecionar o link GizmosCancelAsync , chama-se a página GizmosCancelAsync.aspx e demonstra o cancelamento (por temporização) da chamada assíncrona. Como o tempo de atraso está dentro de um intervalo aleatório, pode ser necessário atualizar a página algumas vezes para obter a mensagem de erro de timeout.
Configuração do Servidor para Chamadas de Serviço Web de Alta Concorrência/Alta Latência
Para perceber os benefícios de uma aplicação web assíncrona, pode ser necessário fazer algumas alterações à configuração padrão do servidor. Tenha o seguinte em mente ao configurar e testar a sua aplicação web assíncrona.
O Windows 7, Windows Vista, Windows 8 e todos os sistemas operativos clientes Windows têm um máximo de 10 pedidos simultâneos. Vai precisar de um sistema operativo Windows Server para ver os benefícios dos métodos assíncronos sob alta carga.
Registe o .NET 4.5 com IIS a partir de um prompt de comandos elevado usando o seguinte comando:
%windir%\Microsoft.NET\Framework64 \v4.0.30319\aspnet_regiis -i
Consulte Ferramenta de Registo IIS do ASP.NET (Aspnet_regiis.exe)Pode ser necessário aumentar o limite da fila do HTTP.sys do valor padrão de 1.000 para 5.000. Se a definição estiver demasiado baixa, pode ver HTTP.sys rejeitar pedidos com um estado HTTP 503. Para alterar o limite de fila de HTTP.sys:
- Abra o gestor IIS e navegue até ao painel de Pools de Aplicações.
- Clique com o botão direito no pool de aplicações alvo e selecione Definições Avançadas.
- Na caixa de diálogo Definições Avançadas , altere o Comprimento da Fila de 1.000 para 5.000.
Note nas imagens acima, o .NET framework está listado como v4.0, mesmo que o pool de aplicações use .NET 4.5. Para compreender esta discrepância, veja o seguinte:
.NET Versioning and Multi-Targeting - .NET 4.5 é uma atualização no local para .NET 4.0
Como configurar uma aplicação IIS ou AppPool para usar ASP.NET 3.5 em vez de 2.0
Se a sua aplicação estiver a usar serviços web ou System.NET para comunicar com um backend via HTTP, pode ser necessário aumentar o elemento connectionManagement/maxconnection . Para ASP.NET aplicações, isto é limitado pela funcionalidade autoConfig a 12 vezes o número de CPUs. Isto significa que, num quad-proc, podes ter no máximo 12 * 4 = 48 ligações concorrentes a um ponto final IP. Como isto está ligado ao autoConfig, a forma mais fácil de aumentar
maxconnectionnuma aplicação ASP.NET é definir programaticamente o System.Net.ServicePointManager.DefaultConnectionLimit no método fromApplication_Startno ficheiro global.asax . Veja o ficheiro de amostra para um exemplo.No .NET 4.5, o padrão de 5000 para MaxConcurrentRequestsPerCPU deve ser suficiente.