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.
Quando uma API é demasiado lenta, a tua aplicação .NET recebe uma de duas exceções, dependendo do timeout que foi disparado.
HttpClient.Timeout lança um TaskCanceledException. O processador padrão de resiliência proveniente de Microsoft.Extensions.Http.Resilience lança um TimeoutRejectedException da Polly. Vêm de sítios diferentes, têm predefinições diferentes e precisam de blocos separados catch.
Qual foi o timeout que ocorreu
| Timeout | Default | O que o teu código recebe | Onde o colocaste |
|---|---|---|---|
HttpClient.Timeout |
100 segundos |
TaskCanceledException, com a TimeoutException como seu InnerException (.NET 5 e posteriores) |
HttpClient.Timeout |
| Timeout da tentativa do manipulador padrão | 10 segundos por tentativa | Nada no início: o processador repete a tentativa | AddStandardResilienceHandler(options => ...) |
| Tempo limite total do processador predefinido | 30 segundos, incluindo todas as novas tentativas | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout aplica-se a todos os pedidos que a HttpClient instância envia. Para usar um timeout diferente para um pedido, passe a CancellationToken de um CancellationTokenSource com o seu próprio timeout. O mais curto dos dois aplica-se. Defina Timeout.InfiniteTimeSpan para o desligar.
No .NET 5 e seguintes, um timeout lança um TaskCanceledException com um TimeoutException no interior. Nas versões anteriores do .NET Core, a exceção interna não existe. No .NET Framework, obtém-se um HttpRequestException em vez disso. Para detalhes, consulte HttpClient.Timeout e faça pedidos HTTP com a classe HttpClient.
Um erro TaskCanceledException também significa que alguém cancelou o pedido, por exemplo, um utilizador que fechou a página. Para distinguir um timeout de um cancelamento, verifique ex.InnerException is TimeoutException ou verifique se o seu próprio token está cancelado.
O manipulador de resiliência padrão
AddStandardResilienceHandler() Encadeia um limitador de taxa, um timeout total, uma retentativa, um disjuntor de circuito e um timeout de tentativa. Quando uma tentativa demora mais de 10 segundos, o tempo limite da tentativa cancela-a e a estratégia de repetição tenta novamente: até 3 repetições, com recuo exponencial e variação aleatória, começando aos 2 segundos. Quando todo o pedido, incluindo novas tentativas, demora mais de 30 segundos, o timeout total cancela-o e o seu código recebe um erro TimeoutRejectedException.
TimeoutRejectedException deriva de Exception. Não é nem a TimeoutException nem um HttpRequestException, por isso um bloco catch (HttpRequestException) não o apanha. Para a lista completa de predefinições, veja predefinições do processador de resiliência padrão.
Por exemplo, quando uma aplicação .NET 10 que usa os valores predefinidos do handler padrão chama uma API que demora entre 11 a 15 segundos por resposta, a aplicação recebe um TimeoutRejectedException após 30 segundos, e o seu bloco catch (HttpRequestException) não é executado.
Como gerir os timeouts do HttpClient
- Capture ambas as exceções na chamada à API. Se usares o handler padrão, captura
TimeoutRejectedException. ObtémTaskCanceledExceptionparaHttpClient.Timeout. - Distinga um tempo limite de um cancelamento. Só trate a
TaskCanceledExceptioncomo um timeout quando a sua exceção interna for umTimeoutException. Quando o interlocutor cancelou, pare silenciosamente. - Escolha tempos de espera adequados à API. Se a API demorar frequentemente mais de 10 segundos, altere os tempos limite da tentativa e o tempo limite total em
AddStandardResilienceHandler(options => ...). - Não volte a tentar
POSTouPATCHa não ser que a API o torne seguro. O handler padrão repete as tentativas de todos os métodos por defeito, incluindoPOST. Chamaroptions.Retry.DisableForUnsafeHttpMethods()para excluirPOST,PATCH,PUT,DELETEeCONNECT, ouoptions.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch)para continuar a tentar novamentePUTeDELETEidempotentes. - Diz ao utilizador o que aconteceu. Mostra "o serviço está lento, tenta novamente" em vez de um erro genérico.
using Polly.Timeout;
public async Task<string?> GetForecastAsync(HttpClient client, CancellationToken cancellationToken)
{
try
{
return await client.GetStringAsync("https://api.contoso.com/forecast", cancellationToken);
}
catch (TimeoutRejectedException)
{
// Standard resilience handler: total timeout expired after all retries
return null;
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
// HttpClient.Timeout expired
return null;
}
catch (HttpRequestException)
{
// Network error, or an error status code after all retries
return null;
}
}
Como testar se a sua aplicação lida com tempos limite
Raramente vês um timeout enquanto desenvolves, por isso os teus blocos catch raramente são executados. A forma como testas decide se encontras os bugs antes dos teus utilizadores.
| Approach | O que encontra | Do que sentes falta |
|---|---|---|
| Aguardar o ambiente de produção | Timeouts reais | Tudo, até que um utilizador lhe toque |
| Simula a API nos teus testes, ou deixa o teu agente de programação escrever o mock | Se o teu catch bloco é executado, se o mock lança a exceção certa |
A tua verdadeira HttpClient configuração, as tentativas do handler de resiliência, e a exceção que ele realmente lança. A tua aplicação também precisa de um switch só de teste para chegar ao mock. |
| Chame a API real e espere que seja lenta | Comportamento real | Não podes tornar a API lenta quando quiseres |
| Intercete o tráfego real da sua aplicação e atrase as respostas | O seu verdadeiro HttpClient, manipulador de resiliência e exceções |
Nada na tua aplicação muda, por isso isso não testa o teu código isoladamente. Deixa isso para os testes unitários. |
Experimente-o na sua aplicação
Dev Proxy interceta os pedidos da sua aplicação para a API e atrasa as respostas com o LatencyPlugin. O .NET usa o proxy do sistema, por isso não precisas de alterar o teu código. Este exemplo atrasa cada resposta em 11 a 15 segundos, mais tempo do que o tempo de espera de tentativa de 10 segundos do manipulador padrão. Substitui https://api.contoso.com pelo URL da API que a tua app chama.
Ficheiro: devproxyrc.json
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
"plugins": [
{
"name": "LatencyPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
"configSection": "slowApi"
}
],
"urlsToWatch": [
"https://api.contoso.com/*"
],
"slowApi": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/latencyplugin.schema.json",
"minMs": 11000,
"maxMs": 15000
}
}
Inicia o Dev Proxy com devproxy --config-file devproxyrc.json e executa a tua aplicação. Cada tentativa atinge o tempo limite, o handler tenta novamente e, após 30 segundos, o seu código recebe um TimeoutRejectedException. Para testar HttpClient.Timeout , defina minMs um valor superior ao timeout que configurou. Para detalhes de configuração, veja Usar Proxy de Desenvolvimento com aplicações .NET. Para instalar Dev Proxy, consulte Configurar Dev Proxy.