Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
por Rick Anderson
Este tutorial ensinará os conceitos básicos da criação de um aplicativo assíncrono ASP.NET Web Forms usando o Visual Studio Express 2012 para Web, que é uma versão gratuita do Microsoft Visual Studio. Você também pode usar o Visual Studio 2012. As seções a seguir são incluídas neste tutorial.
- Como as solicitações são processadas pelo pool de threads
- Escolhendo métodos síncronos ou assíncronos
- O aplicativo de exemplo
- A página Síncrona do Gizmos
- Criando uma página de Gizmos assíncrona
- Executando várias operações em paralelo
- Usando um token de cancelamento
- Configuração do servidor para chamadas de serviço Web de alta simultaneidade/alta latência
Um exemplo completo é fornecido para este tutorial em
https://github.com/RickAndMSFT/Async-ASP.NET/ no site do GitHub .
Páginas da Web do ASP.NET 4.5 em combinação com .NET 4.5 permite que você registre métodos assíncronos que retornam um objeto do tipo Task. O .NET Framework 4 introduziu um conceito de programação assíncrona conhecido como Tarefa e ASP.NET 4.5 dá suporte à Tarefa. As tarefas são representadas pelo tipo de tarefa e tipos relacionados no namespace System.Threading.Tasks . O .NET Framework 4.5 baseia-se nesse suporte assíncrono com as palavras-chave await e assíncronas que tornam o trabalho com objetos Task muito menos complexo do que as abordagens assíncronas anteriores. A palavra-chave await é uma abreviação sintática para indicar que um pedaço de código deve aguardar de forma assíncrona algum outro código. A palavra-chave assíncrona representa uma dica que você pode usar para marcar métodos como métodos assíncronos baseados em tarefa. A combinação de await, async e objeto Task facilita muito a gravação de código assíncrono no .NET 4.5. O novo modelo para métodos assíncronos é chamado de TAP (Padrão Assíncrono baseado em tarefa). Este tutorial pressupõe que você tenha alguma familiaridade com a programação assíncrona usando palavras-chave await e assíncronas e o namespace Tarefa .
Para obter mais informações sobre o uso das palavras-chave await e async e o namespace Task, consulte as referências a seguir.
- Whitepaper: Assincronismo no .NET
- Perguntas Frequentes sobre Async/Await
- Programação assíncrona do Visual Studio
Como as solicitações são processadas pelo pool de threads
No servidor Web, o .NET Framework mantém um pool de threads que são usados para atender a solicitações de ASP.NET. Quando uma solicitação chega, um thread do pool é enviado para processar essa solicitação. Se a solicitação for processada de forma síncrona, o thread que processa a solicitação estará ocupado enquanto a solicitação estiver sendo processada e esse thread não poderá atender a outra solicitação.
Isso pode não ser um problema, pois 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 aplicativos grandes com alta simultaneidade de solicitações de execução longa, todos os threads disponíveis podem estar ocupados. Essa condição é conhecida como fome de thread. Quando essa condição é atingida, o servidor Web enfileira solicitações. Se a fila de solicitações ficar cheia, o servidor Web rejeitará solicitações com um status HTTP 503 (Servidor Muito Ocupado). O pool de threads do CLR possui limitações na injeção de novas threads. Se a simultaneidade for intermitente (ou seja, seu site pode receber repentinamente um grande número de solicitações) e todos os threads de solicitação disponíveis estiverem ocupados por causa de chamadas de back-end de alta latência, a taxa limitada de injeção de thread pode fazer com que seu aplicativo tenha um desempenho muito ruim. Além disso, cada novo thread adicionado ao pool de threads apresenta sobrecarga (como 1 MB de memória de pilha). Um aplicativo Web usando métodos síncronos para atender chamadas de alta latência em que o pool de threads aumenta para o máximo padrão do .NET 4.5 de 5.000 threads consumiria aproximadamente 5 GB a mais de memória do que um aplicativo capaz de fazer o serviço com as mesmas solicitações usando métodos assíncronos e apenas 50 threads. Quando você está fazendo um trabalho assíncrono, nem sempre está usando um thread. Por exemplo, quando você fizer uma solicitação de serviço Web assíncrona, ASP.NET não usará threads entre a chamada do método assíncrono e a espera. Usar o pool de threads para atender a solicitações com alta latência pode levar a um volume de memória grande e a uma utilização ruim do hardware do servidor.
Processando solicitações assíncronas
Em aplicativos web que apresentam um grande número de solicitações simultâneas na inicialização ou uma carga de rajada (onde a concorrência aumenta repentinamente), tornar as chamadas de serviços web assíncronas aumentará a capacidade de resposta do aplicativo. Uma solicitação assíncrona leva o mesmo tempo para ser processada como uma solicitação síncrona. Por exemplo, se uma solicitação fizer uma chamada de serviço Web que requer dois segundos para ser concluída, a solicitação levará dois segundos se ela for executada de forma síncrona ou assíncrona. No entanto, durante uma chamada assíncrona, um thread não é impedido de responder a outras solicitações enquanto aguarda a conclusão da primeira solicitação. Portanto, solicitações assíncronas impedem o crescimento da fila de solicitações e do pool de threads quando há muitas solicitações simultâneas que invocam operações de execução longa.
Escolhendo métodos síncronos ou assíncronos
Esta seção lista as diretrizes para quando usar métodos síncronos ou assíncronos. Estas são apenas diretrizes; examine cada aplicativo individualmente para determinar se os métodos assíncronos ajudam com o desempenho.
Em geral, use métodos síncronos para as seguintes condições:
- As operações são simples ou de execução curta.
- 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 envolvem sobrecarga de disco ou rede extensa. O uso de métodos assíncronos em operações associadas à CPU não oferece benefícios e resulta em mais sobrecarga.
Em geral, use métodos assíncronos para as seguintes condições:
Você está chamando serviços que podem ser consumidos por meio de métodos assíncronos e está usando o .NET 4.5 ou superior.
As operações são associadas à rede ou associadas à E/S em vez de associadas à CPU.
O paralelismo é mais importante do que a simplicidade do código.
Você deseja fornecer um mecanismo que permita que os usuários cancelem uma solicitação de execução prolongada.
Quando o benefício de alternar threads supera o custo da troca de contexto. Em geral, você deverá tornar um método assíncrono se o método síncrono bloquear o thread de solicitação ASP.NET enquanto não faz nenhum trabalho. Ao tornar a chamada assíncrona, o thread de solicitação ASP.NET não é bloqueado, não funcionando enquanto aguarda a conclusão da solicitação do serviço Web.
O teste mostra que as operações de bloqueio são um gargalo no desempenho do site e que o IIS pode atender a mais solicitações usando métodos assíncronos para essas chamadas de bloqueio.
O exemplo para download mostra como usar métodos assíncronos com eficiência. O exemplo fornecido foi projetado para fornecer uma demonstração simples de programação assíncrona no ASP.NET 4.5. O exemplo não se destina a ser uma arquitetura de referência para programação assíncrona no ASP.NET. O programa de exemplo chama métodos da API Web do ASP.NET, que, por sua vez, chamam Task.Delay para simular chamadas de serviço Web de longa execução. A maioria dos aplicativos de produção não mostrará esses benefícios óbvios para usar métodos assíncronos.
Poucos aplicativos exigem que todos os métodos sejam assíncronos. Muitas vezes, converter alguns métodos síncronos em métodos assíncronos fornece o melhor aumento de eficiência para a quantidade de trabalho necessária.
O aplicativo de exemplo
Você pode baixar a aplicação de exemplo de https://github.com/RickAndMSFT/Async-ASP.NET no site do GitHub. O repositório consiste em três projetos:
- WebAppAsync: o projeto ASP.NET Web Forms que consome o serviço WebAPIpwg da API Web. A maior parte do código deste tutorial é deste projeto.
-
WebAPIpgw: o projeto de API Web do ASP.NET MVC 4 que implementa os
Products, Gizmos and Widgetscontroladores. Ele fornece os dados para o projeto WebAppAsync e o projeto Mvc4Async . - Mvc4Async: o projeto ASP.NET MVC 4 que contém o código usado em outro tutorial. Ele faz chamadas à API Web para o serviço WebAPIpwg .
A página Síncrona do Gizmos
O código a seguir mostra o Page_Load método síncrono usado para exibir uma lista de aparelhos. (Para este artigo, um gizmo é 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 a seguir mostra o GetGizmos método do serviço de 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 API Web ASP.NET que retorna uma lista de dados de dispositivos. O projeto WebAPIpgw contém a implementação da Web API gizmos, widget e dos controladores product.
A imagem a seguir mostra a página de dispositivos do projeto exemplo.
Criando uma página assíncrona de gizmos
O exemplo usa as novas palavras-chave assíncronas e de espera (disponíveis no .NET 4.5 e no Visual Studio 2012) para permitir que o compilador seja responsável por manter as transformações complicadas necessárias para programação assíncrona. O compilador permite que você escreva código usando os constructos de fluxo de controle síncrono do C#e o compilador aplica automaticamente as transformações necessárias para usar retornos de chamada para evitar o bloqueio de threads.
ASP.NET páginas assíncronas devem incluir a diretiva Page com o Async atributo definido como "true". O código a seguir mostra a diretiva Page com o Async atributo definido como "true" para a página GizmosAsync.aspx .
<%@ Page Async="true" Language="C#" AutoEventWireup="true"
CodeBehind="GizmosAsync.aspx.cs" Inherits="WebAppAsync.GizmosAsync" %>
O código a seguir mostra o Gizmos método síncrono Page_Load e a GizmosAsync página assíncrona. Se seu navegador der suporte ao
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 a seguir foram aplicadas para permitir que a GizmosAsync página seja assíncrona.
- A diretiva Page deve ter o
Asyncatributo definido como "true". - O
RegisterAsyncTaskmétodo é usado para registrar uma tarefa assíncrona que contém o código que é executado de forma assíncrona. - O novo
GetGizmosSvcAsyncmétodo é marcado com a palavra-chave async, que instrui o compilador a gerar callbacks para partes do corpo e a criar automaticamente umTaskque é retornado. - "Async" foi acrescentado ao nome do método assíncrono. Acrescentar "Async" não é necessá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 contínuo e oferece aos chamadores do método uma maneira de aguardar a conclusão da operação assíncrona. - A palavra-chave await foi aplicada à chamada do serviço Web.
- A API do serviço Web assíncrono foi chamada (
GetGizmosAsync).
Dentro do corpo do GetGizmosSvcAsync método, outro método assíncrono é GetGizmosAsync chamado.
GetGizmosAsync retorna imediatamente um Task<List<Gizmo>> que será concluído assim que os dados estiverem disponíveis. Como você não deseja fazer mais nada até ter os dados do gizmo, o código aguarda a tarefa (usando a palavra-chave await ). Você pode usar a palavra-chave await somente em métodos anotados com a palavra-chave assíncrona .
A palavra-chave await não bloqueia o thread até que a tarefa seja concluída. Ele registra o restante do método como um callback na tarefa e retorna imediatamente. Quando a tarefa aguardada for concluída, ela invocará esse retorno de chamada e, portanto, retomará a execução do método exatamente de onde parou. Para obter mais informações sobre como usar as palavras-chave await e async e o namespace Task, consulte as referências async.
O código a seguir mostra os métodos GetGizmos 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
Task<List<Gizmo>>e Async foi acrescentado 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 HttpClientGetAsync .
A imagem a seguir mostra o modo de exibição de gizmo assíncrono.
A apresentação dos dados de gizmos nos navegadores é idêntica à exibição criada pela chamada síncrona. A única diferença é que a versão assíncrona pode ter um desempenho maior em cargas pesadas.
Notas de RegisterAsyncTask
Os métodos ligados RegisterAsyncTask serão executados imediatamente após PreRender.
Se você usar eventos de página nulos assíncronos diretamente, conforme mostrado no código a seguir:
protected async void Page_Load(object sender, EventArgs e) {
await ...;
// do work
}
você não tem mais controle total sobre quando os eventos são executados. Por exemplo, se tanto um arquivo .aspx quanto um arquivo .Master definirem eventos Page_Load e um ou ambos forem assíncronos, a ordem de execução não pode ser garantida. A mesma ordem indeterminada para manipuladores de eventos (como async void Button_Click ) se aplica.
Executando várias operações em paralelo
Métodos assíncronos têm uma vantagem significativa sobre métodos síncronos quando uma ação deve executar várias operações independentes. No exemplo fornecido, a página síncrona PWG.aspx(para Produtos, Widgets e Gadgets) exibe os resultados de três chamadas de serviço Web para obter uma lista de produtos, widgets e gadgets. O ASP.NET projeto de API Web que fornece esses serviços usa Task.Delay para simular latência ou chamadas de rede lentas. Quando o atraso é definido como 500 milissegundos, a página assíncrona PWGasync.aspx leva pouco mais de 500 milissegundos para ser concluída enquanto a versão síncrona PWG leva mais de 1.500 milissegundos. A página PWG.aspx síncrona é mostrada no código a seguir.
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 a seguir mostra a visualização retornada da página assíncrona PWGasync.aspx.
Usando um token de cancelamento
Os métodos assíncronos retornados Tasksão canceláveis, ou seja, eles tomam um parâmetro CancellationToken quando um é fornecido com o AsyncTimeout atributo da diretiva Page . O código a seguir mostra a página GizmosCancelAsync.aspx com um tempo limite de um segundo.
<%@ Page Async="true" AsyncTimeout="1"
Language="C#" AutoEventWireup="true"
CodeBehind="GizmosCancelAsync.aspx.cs"
Inherits="WebAppAsync.GizmosCancelAsync" %>
O código a seguir mostra o arquivo 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);
}
}
No aplicativo de exemplo fornecido, selecionar o link GizmosCancelAsync chama a página GizmosCancelAsync.aspx e demonstra o cancelamento (por tempo limite) da chamada assíncrona. Como o tempo de atraso está dentro de um intervalo aleatório, talvez seja necessário atualizar a página algumas vezes para obter a mensagem de erro de tempo limite.
Configuração do servidor para chamadas de serviço Web de alta simultaneidade/alta latência
Para obter os benefícios de um aplicativo Web assíncrono, talvez seja necessário fazer algumas alterações na configuração do servidor padrão. Tenha o seguinte em mente ao configurar e testar por estresse seu aplicativo Web assíncrono.
Os sistemas operacionais cliente Windows 7, Windows Vista, Window 8 e todos os sistemas operacionais cliente Windows têm no máximo 10 solicitações simultâneas. Você precisará de um sistema operacional Windows Server para ver os benefícios dos métodos assíncronos sob alta carga.
Registre o .NET 4.5 no IIS a partir de um prompt de comando com permissões elevadas usando o seguinte comando:
%windir%\Microsoft.NET\Framework64 \v4.0.30319\aspnet_regiis -i
Consulte ASP.NET IIS Registration Tool (Aspnet_regiis.exe)Talvez seja necessário aumentar o limite de filaHTTP.sys do valor padrão de 1.000 para 5.000. Se a configuração for muito baixa, você poderá ver HTTP.sys rejeitar solicitações com um status HTTP 503. Para alterar o limite de fila HTTP.sys:
- Abra o gerenciador do IIS e navegue até o painel Pools de Aplicativos.
- Clique com o botão direito do mouse no pool de aplicativos de destino e selecione Configurações Avançadas.
- Na caixa de diálogo Configurações Avançadas , altere o Comprimento da Fila de 1.000 para 5.000.
Observe que, nas imagens acima, a estrutura do .NET é listada como v4.0, mesmo que o pool de aplicativos esteja usando o .NET 4.5. Para entender essa discrepância, confira o seguinte:
Controle de versão do .NET e multidestinação – o .NET 4.5 é uma atualização local para o .NET 4.0
Como definir um aplicativo IIS ou AppPool para usar ASP.NET 3.5 em vez de 2.0
Se o aplicativo estiver usando serviços Web ou System.NET para se comunicar com um back-end via HTTP, talvez seja necessário aumentar o elemento connectionManagement/maxconnection . Para aplicativos ASP.NET, isso é limitado pelo recurso de configuração automática a 12 vezes o número de CPUs. Isso significa que, em um quad-proc, você pode ter no máximo 12 * 4 = 48 conexões simultâneas com um ponto de extremidade IP. Como isso está vinculado à configuração automática, a maneira mais fácil de aumentar
maxconnectionem um aplicativo ASP.NET é definir System.Net.ServicePointManager.DefaultConnectionLimit programaticamente no método fromApplication_Startno arquivo global.asax . Consulte o download de exemplo para obter um exemplo.No .NET 4.5, o padrão de 5000 para MaxConcurrentRequestsPerCPU deve ser bom.