Eventos
Campeonato Mundial de Visualização de Dados do Power BI
14 de fev., 16 - 31 de mar., 16
Com 4 chances de participar, você pode ganhar um pacote de conferência e chegar à Grande Final AO VIVO em Las Vegas
Saiba maisNão há mais suporte para esse navegador.
Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Por Mike Rousos
Este artigo fornece diretrizes para maximizar o desempenho e a confiabilidade dos aplicativos ASP.NET Core.
O cache é discutido em várias partes deste artigo. Para obter mais informações, confira Visão geral do armazenamento em cache no ASP.NET Core.
Neste artigo, um caminho do código dinâmico é definido como um caminho do código que é frequentemente chamado e onde grande parte do tempo de execução ocorre. Os caminhos do código dinâmicos normalmente limitam a expansão e o desempenho do aplicativo e são discutidos em várias partes deste artigo.
Os aplicativos ASP.NET Core devem ser projetados para processar várias solicitações simultaneamente. As APIs assíncronas permitem que um pequeno pool de threads cuide de milhares de solicitações simultâneas sem aguardar chamadas de bloqueio. Em vez de aguardar a conclusão de uma tarefa síncrona de execução longa, o thread pode trabalhar em outra solicitação.
Um problema de desempenho comum em aplicativos ASP.NET Core é o bloqueio de chamadas que poderiam ser assíncronas. Muitas chamadas de bloqueio síncronas levam à Privação do pool de threads e a tempos de resposta degradados.
Não bloqueie a execução assíncrona chamando Task.Wait ou Task<TResult>.Result.
Não adquira bloqueios em caminhos do código comuns. Os aplicativos ASP.NET Core têm o melhor desempenho quando arquitetados para executar código em paralelo.
Não chame Task.Run e aguarde imediatamente. O ASP.NET Core já executa o código do aplicativo em threads de Pool de Threads normais, portanto, chamar Task.Run
resulta apenas no agendamento extra desnecessário do Pool de Threads. Mesmo que o código agendado bloqueie um thread, Task.Run
não impede isso.
Um criador de perfil, como PerfView, pode ser usado para localizar threads adicionados frequentemente ao Pool de threads. O evento Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start
indica um thread adicionado ao pool de threads.
Uma página da Web não deve carregar grandes quantidades de dados de uma só vez. Ao retornar uma coleção de objetos, considere se ela pode levar a problemas de desempenho. Determine se o design pode produzir os seguintes resultados ruins:
Adicione paginação para atenuar os cenários anteriores. Usando parâmetros de índice e tamanho de página, os desenvolvedores devem privilegiar o design de retorno de um resultado parcial. Quando um resultado exaustivo for exigido, a paginação deverá ser usada para preencher de forma assíncrona lotes de resultados a fim de evitar o bloqueio de recursos do servidor.
Para obter mais informações sobre paginação e limitação do número de registros retornados, confira:
O retorno de IEnumerable<T>
de uma ação resulta na iteração da coleção síncrona pelo serializador. O resultado é o bloqueio de chamadas e um potencial de privação do pool de threads. Para evitar enumeração síncrona, use ToListAsync
antes de retornar o enumerável.
A partir do ASP.NET Core 3.0, IAsyncEnumerable<T>
pode ser usado como uma alternativa para IEnumerable<T>
que enumera de forma assíncrona. Para obter mais informações, confira Tipos de retorno de ação do controlador.
O coletor de lixo do .NET Core gerencia a alocação e a liberação de memória automaticamente em aplicativos do ASP.NET Core. A coleta automática de lixo geralmente significa que os desenvolvedores não precisam se preocupar com como ou quando a memória é liberada. No entanto, a limpeza de objetos não referenciados exige tempo de CPU, ou seja, os desenvolvedores devem minimizar a alocação de objetos em caminhos do código dinâmicos. A coleta de lixo é especialmente cara para objetos grandes (> 85.000 Kb). Objetos grandes são armazenados no heap de objetos grandes e exigem uma coleta de lixo completa (geração 2) para serem limpos. Ao contrário das coleções de geração 0 e de geração 1, uma coleção de geração 2 requer uma suspensão temporária da execução do aplicativo. A alocação e a desalocação frequentes de objetos grandes podem causar um desempenho inconsistente.
Recomendações:
Problemas de memória, como o anterior, podem ser diagnosticados com o exame das estatísticas de GC (coleta de lixo) no PerfView e o exame de:
Para obter mais informações, confira Coleta de Lixo e Desempenho.
As interações com um armazenamento de dados e outros serviços remotos geralmente são as partes mais lentas de um aplicativo ASP.NET Core. Ler e gravar dados com eficiência é fundamental para um bom desempenho.
Recomendações:
.Where
, .Select
ou .Sum
, por exemplo) para que a filtragem seja executada pelo banco de dados.As abordagens a seguir podem melhorar o desempenho em aplicativos de grande escala:
Recomendamos medir o impacto das abordagens de alto desempenho anteriores antes de confirmar a base de código. A complexidade adicional das consultas compiladas pode não justificar a melhoria de desempenho.
Os problemas de consulta podem ser detectados com o exame do tempo gasto acessando dados com o Application Insights ou com ferramentas de criação de perfil. A maioria dos bancos de dados também disponibiliza estatísticas sobre consultas executadas com frequência.
Embora HttpClient implemente a interface IDisposable
, ela foi projetada para reutilização. Instâncias HttpClient
fechadas deixam soquetes abertos no estado TIME_WAIT
por um curto período de tempo. Se um caminho do código que cria e descarta objetos HttpClient
é usado com frequência, o aplicativo pode esgotar os soquetes disponíveis. HttpClientFactory
foi apresentado no ASP.NET Core 2.1 como uma solução para esse problema. Ele manipula o pool de conexões HTTP para otimizar o desempenho e a confiabilidade. Para obter mais informações, confira Usar HttpClientFactory
para implementar solicitações HTTP resilientes.
Recomendações:
HttpClient
diretamente.HttpClient
. Para obter mais informações, consulte Usar o HttpClientFactory para implementar solicitações HTTP resilientes.Você quer que todo o código seja rápido. Os caminhos do código chamados com frequência são os que mais devem ser otimizados. Estão incluídos:
Recomendações:
A maioria das solicitações para um aplicativo ASP.NET Core pode ser manipulada por um controlador ou modelo de página que chama os serviços necessários e retorna uma resposta HTTP. Para algumas solicitações que envolvem tarefas de execução longa, é melhor tornar todo o processo de solicitação-resposta assíncrono.
Recomendações:
Os aplicativos ASP.NET Core com front-ends complexos normalmente atendem a muitos arquivos JavaScript, CSS ou de imagem. O desempenho das solicitações de carga iniciais pode ser aprimorado:
Recomendações:
environment
do ASP.NET Core para lidar com ambientes de Development
e de Production
.A redução do tamanho da resposta geralmente aumenta a capacidade de resposta de um aplicativo, muitas vezes dramaticamente. Uma maneira de reduzir os tamanhos de carga é compactar as respostas de um aplicativo. Para obter mais informações, confira Compactação de resposta.
Cada nova versão do ASP.NET Core inclui aprimoramentos de desempenho. Otimizações no .NET Core e no ASP.NET Core significam que as versões mais recentes geralmente superam as versões mais antigas. Por exemplo, o .NET Core 2.1 adicionou suporte a expressões regulares compiladas e se beneficiou de Span<T>. O ASP.NET Core 2.2 adicionou suporte ao HTTP/2. O ASP.NET Core 3.0 adiciona muitos aprimoramentos que reduzem o uso de memória e melhoram a taxa de transferência. Se o desempenho for uma prioridade, considere atualizar para a versão atual do ASP.NET Core.
As exceções devem ser raras. O lançamento e a captura de exceções são lentos em relação a outros padrões de fluxo de código. Por isso, as exceções não devem ser usadas para controlar o fluxo normal do programa.
Recomendações:
Ferramentas de diagnóstico de aplicativo, como o Application Insights, podem ajudar a identificar exceções comuns em um aplicativo que podem afetar o desempenho.
Toda a E/S no ASP.NET Core é assíncrona. Os servidores implementam a interface Stream
, que tem sobrecargas síncronas e assíncronas. As assíncronas devem ser preferidas para evitar o bloqueio de threads do pool de threads. O bloqueio de threads pode levar à privação do pool de threads.
Não faça o seguinte: O exemplo a seguir usa o ReadToEnd. Ele bloqueia o thread atual para aguardar o resultado. Este é um exemplo de síncrono em vez de assíncrono.
public class BadStreamReaderController : Controller
{
[HttpGet("/contoso")]
public ActionResult<ContosoData> Get()
{
var json = new StreamReader(Request.Body).ReadToEnd();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
No código anterior, Get
lê de forma síncrona todo o corpo da solicitação HTTP na memória. Se o cliente está carregando lentamente, o aplicativo está usando síncrono em vez de assíncrono. O aplicativo usa síncrono em vez de assíncrono porque o KestrelNÃO dá suporte a leituras síncronas.
Faça o seguinte: O exemplo a seguir usa ReadToEndAsync e não bloqueia o thread durante a leitura.
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
var json = await new StreamReader(Request.Body).ReadToEndAsync();
return JsonSerializer.Deserialize<ContosoData>(json);
}
}
O código anterior lê de forma síncrona todo o corpo da solicitação HTTP na memória.
Aviso
Se a solicitação for grande, a leitura de todo o corpo da solicitação HTTP na memória poderá levar a uma condição de OOM (memória insuficiente). O OOM pode resultar em uma negação de serviço. Para obter mais informações, confira Evitar ler corpos de solicitação grandes ou corpos de resposta na memória neste artigo.
Faça o seguinte: O exemplo abaixo é totalmente assíncrono e usa um corpo de solicitação sem buffer:
public class GoodStreamReaderController : Controller
{
[HttpGet("/contoso")]
public async Task<ActionResult<ContosoData>> Get()
{
return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
}
}
O código anterior desserializa de forma assíncrona o corpo da solicitação em um objeto C#.
Use HttpContext.Request.ReadFormAsync
em vez de HttpContext.Request.Form
.
HttpContext.Request.Form
pode ser lido com segurança apenas nas seguintes condições:
ReadFormAsync
eHttpContext.Request.Form
Não faça o seguinte: O exemplo a seguir usa HttpContext.Request.Form
. HttpContext.Request.Form
usa síncrono em vez de assíncrono e pode levar à privação do pool de threads.
public class BadReadController : Controller
{
[HttpPost("/form-body")]
public IActionResult Post()
{
var form = HttpContext.Request.Form;
Process(form["id"], form["name"]);
return Accepted();
}
Faça o seguinte: O exemplo a seguir usa HttpContext.Request.ReadFormAsync
para ler o corpo do formulário de forma assíncrona.
public class GoodReadController : Controller
{
[HttpPost("/form-body")]
public async Task<IActionResult> Post()
{
var form = await HttpContext.Request.ReadFormAsync();
Process(form["id"], form["name"]);
return Accepted();
}
No .NET, cada alocação de objeto maior ou igual a 85.000 bytes acaba no large object heap (LOH). Objetos grandes são caros por dois motivos:
Esta postagem no blog descreve o problema de forma sucinta:
Quando um objeto grande é alocado, ele é marcado como objeto Gen 2. Não Gen 0 como para objetos pequenos. As consequências são que, se você ficar sem memória no LOH, o GC limpará todo o heap gerenciado, não apenas o LOH. Assim, ele limpa Gen 0, Gen 1 e Gen 2, incluindo o LOH. Isso é chamado de coleta de lixo completa e é a coleta de lixo mais demorada. Para muitos aplicativos, pode ser aceitável. Mas definitivamente não para servidores Web de alto desempenho, em que poucos buffers de memória grandes são necessários para lidar com uma solicitação da Web mediana (ler de um soquete, descompactar, decodificar JSON entre outros).
O armazenamento de um corpo de solicitação ou resposta grande em um único byte[]
ou string
:
Ao usar um serializador/desserializador que só dá suporte a leituras e gravações síncronas (por exemplo, Json.NET):
Aviso
Se a solicitação for grande, ela poderá levar a uma condição de OOM (memória insuficiente). O OOM pode resultar em uma negação de serviço. Para obter mais informações, confira Evitar ler corpos de solicitação grandes ou corpos de resposta na memória neste artigo.
O ASP.NET Core 3.0 usa System.Text.Json por padrão para serialização JSON. System.Text.Json:
Newtonsoft.Json
.O IHttpContextAccessor.HttpContext retorna oHttpContext
da solicitação ativa quando acessado no thread de solicitação. O IHttpContextAccessor.HttpContext
não deve ser armazenado em um campo ou variável.
Não faça o seguinte: O exemplo a seguir armazena HttpContext
um campo e tenta usá-lo mais tarde.
public class MyBadType
{
private readonly HttpContext _context;
public MyBadType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
O código anterior captura frequentemente um HttpContext
nulo ou incorreto no construtor.
Faça o seguinte: Este exemplo:
HttpContext
no momento correto e verifica null
.public class MyGoodType
{
private readonly IHttpContextAccessor _accessor;
public MyGoodType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
}
HttpContext
não é seguros para threads. O acesso a HttpContext
de vários threads em paralelo pode resultar em comportamento inesperado, como falta de resposta do servidor, falhas e corrupção de dados.
Não faça o seguinte: O exemplo a seguir faz três solicitações paralelas e registra o caminho de solicitação de entrada antes e depois da solicitação HTTP de saída. O caminho da solicitação é acessado de vários threads, possivelmente em paralelo.
public class AsyncBadSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
var query1 = SearchAsync(SearchEngine.Google, query);
var query2 = SearchAsync(SearchEngine.Bing, query);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
HttpContext.Request.Path);
searchResults = _searchService.Search(engine, query);
_logger.LogInformation("Finishing search query from {path}.",
HttpContext.Request.Path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}",
HttpContext.Request.Path);
}
return await searchResults;
}
Faça o seguinte: O exemplo a seguir copia todos os dados da solicitação de entrada antes de fazer as três solicitações paralelas.
public class AsyncGoodSearchController : Controller
{
[HttpGet("/search")]
public async Task<SearchResults> Get(string query)
{
string path = HttpContext.Request.Path;
var query1 = SearchAsync(SearchEngine.Google, query,
path);
var query2 = SearchAsync(SearchEngine.Bing, query, path);
var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);
await Task.WhenAll(query1, query2, query3);
var results1 = await query1;
var results2 = await query2;
var results3 = await query3;
return SearchResults.Combine(results1, results2, results3);
}
private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
string path)
{
var searchResults = _searchService.Empty();
try
{
_logger.LogInformation("Starting search query from {path}.",
path);
searchResults = await _searchService.SearchAsync(engine, query);
_logger.LogInformation("Finishing search query from {path}.", path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed query from {path}", path);
}
return await searchResults;
}
HttpContext
só é válido desde que haja uma solicitação HTTP ativa no pipeline do ASP.NET Core. Todo o pipeline do ASP.NET Core é uma cadeia assíncrona de delegados que executa todas as solicitações. Quando o Task
retornado dessa cadeia é concluído, o HttpContext
é reciclado.
Não faça o seguinte: O exemplo abaixo usa async void
, o que faz com que a solicitação HTTP seja concluída quando o primeiro await
é atingido:
async void
é SEMPRE uma prática incorreta em aplicativos do ASP.NET Core.HttpResponse
após a conclusão da solicitação HTTP.public class AsyncBadVoidController : Controller
{
[HttpGet("/async")]
public async void Get()
{
await Task.Delay(1000);
// The following line will crash the process because of writing after the
// response has completed on a background thread. Notice async void Get()
await Response.WriteAsync("Hello World");
}
}
Faça o seguinte: O exemplo a seguir retorna um Task
para a estrutura, ou seja, a solicitação HTTP não é concluída até que a ação seja concluída.
public class AsyncGoodTaskController : Controller
{
[HttpGet("/async")]
public async Task Get()
{
await Task.Delay(1000);
await Response.WriteAsync("Hello World");
}
}
Não faça o seguinte: O exemplo abaixo mostra que um fechamento está capturando o HttpContext
da propriedade Controller
. Essa é uma prática incorreta porque o item de trabalho pode:
HttpContext
.[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
var path = HttpContext.Request.Path;
Log(path);
});
return Accepted();
}
Faça o seguinte: Este exemplo:
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
string path = HttpContext.Request.Path;
_ = Task.Run(async () =>
{
await Task.Delay(1000);
Log(path);
});
return Accepted();
}
As tarefas em segundo plano devem ser implementadas como serviços hospedados. Saiba mais em Tarefas em segundo plano com serviços hospedados.
Não faça o seguinte: O exemplo a seguir mostra um fechamento que está capturando o DbContext
do parâmetro de ação Controller
. Essa é uma má prática. O item de trabalho pode ser executado fora do escopo da solicitação. O ContosoDbContext
tem a solicitação como escopo, resultando em uma ObjectDisposedException
.
[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
});
return Accepted();
}
Faça o seguinte: Este exemplo:
IServiceScopeFactory
é um singleton.ContosoDbContext
da solicitação de entrada.[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
await using (var scope = serviceScopeFactory.CreateAsyncScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
O seguinte código realçado:
ContosoDbContext
do escopo correto.[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory
serviceScopeFactory)
{
_ = Task.Run(async () =>
{
await Task.Delay(1000);
await using (var scope = serviceScopeFactory.CreateAsyncScope())
{
var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();
context.Contoso.Add(new Contoso());
await context.SaveChangesAsync();
}
});
return Accepted();
}
O ASP.NET Core não armazena em buffer o corpo da resposta HTTP. A primeira vez que a resposta é gravada:
Não faça o seguinte: O código abaixo tenta adicionar cabeçalhos de resposta após a resposta já ter sido iniciada:
app.Use(async (context, next) =>
{
await next();
context.Response.Headers["test"] = "test value";
});
No código anterior, context.Response.Headers["test"] = "test value";
lançará uma exceção se next()
tiver gravado a resposta.
Faça o seguinte: O exemplo abaixo verifica se a resposta HTTP foi iniciada antes de modificar os cabeçalhos.
app.Use(async (context, next) =>
{
await next();
if (!context.Response.HasStarted)
{
context.Response.Headers["test"] = "test value";
}
});
Faça o seguinte: O exemplo abaixo usa HttpResponse.OnStarting
para definir os cabeçalhos antes que os cabeçalhos de resposta sejam liberados para o cliente.
Verificar se a resposta não foi iniciada permite registrar um retorno de chamada que será invocado logo antes de os cabeçalhos de resposta serem gravados. Verificar se a resposta não foi iniciada:
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers["someheader"] = "somevalue";
return Task.CompletedTask;
});
await next();
});
Os componentes só esperam ser chamados se for possível manipular a resposta.
Usando uma hospedagem em processo, um aplicativo ASP.NET Core é executado no mesmo processo que seu processo de trabalho do IIS. A hospedagem no processo fornece melhor desempenho em relação à hospedagem fora do processo porque as solicitações não são feitas por proxy no adaptador de loopback. O adaptador de loopback é um adaptador de rede que devolve o tráfego de rede de saída para o mesmo computador. O IIS manipula o gerenciamento de processos com o WAS (Serviço de Ativação de Processos do Windows).
Os projetos são padrão para o modelo de hospedagem no processo no ASP.NET Core 3.0 e posterior.
Para obter mais informações, confira Hospedar o ASP.NET Core no Windows com o IIS
HttpRequest.ContentLength
será nulo se o cabeçalho Content-Length
não for recebido. Nulo, nesse caso, significa que o tamanho do corpo da solicitação não é conhecido; isso não significa que o tamanho seja zero. Como todas as comparações com nulo (exceto ==
) retornam false, o Request.ContentLength > 1024
de comparação, por exemplo, poderá retornar false
quando o tamanho do corpo da solicitação for maior que 1024. Não saber isso pode levar a falhas de segurança em aplicativos. Você pode pensar que está se protegendo contra solicitações muito grandes quando não está.
Para obter mais informações, confira esta resposta do StackOverflow.
Para obter diretrizes sobre como criar um aplicativo ASP.NET Core confiável, seguro, com desempenho, testável e escalonável, consulte padrões de aplicativo Web Enterprise. Um exemplo completo de aplicativo web com qualidade de produção que implementa os padrões está disponível.
Comentários do ASP.NET Core
O ASP.NET Core é um projeto código aberto. Selecione um link para fornecer comentários:
Eventos
Campeonato Mundial de Visualização de Dados do Power BI
14 de fev., 16 - 31 de mar., 16
Com 4 chances de participar, você pode ganhar um pacote de conferência e chegar à Grande Final AO VIVO em Las Vegas
Saiba mais