Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Cuando una API es demasiado lenta, la aplicación de .NET obtiene 1 de 2 excepciones, en función del tiempo de espera que se desencadene.
HttpClient.Timeout lanza una TaskCanceledExceptionexcepción. El controlador de resiliencia estándar de Microsoft.Extensions.Http.Resilience lanza un TimeoutRejectedException de Polly. Proceden de diferentes lugares, tienen valores predeterminados diferentes y necesitan bloques independientes catch .
¿Qué timeout se activó?
| Tiempo de espera | Predeterminado | Qué obtiene tu código | Dónde se configura |
|---|---|---|---|
HttpClient.Timeout |
100 segundos |
TaskCanceledException, con TimeoutException como su InnerException (.NET 5 y versiones posteriores) |
HttpClient.Timeout |
| Tiempo de espera de intento del controlador estándar | 10 segundos por intento | Nada al principio: el controlador vuelve a intentar la operación | AddStandardResilienceHandler(options => ...) |
| Tiempo de espera total del controlador estándar | 30 segundos, incluidos todos los reintentos | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout se aplica a todas las solicitudes que envía la HttpClient instancia. Para usar un tiempo de espera diferente para una solicitud, pase un CancellationToken desde un CancellationTokenSource con su propio tiempo de espera. Se aplica el más corto de los dos. Configura Timeout.InfiniteTimeSpan para desactivarlo.
En .NET 5 y versiones posteriores, un tiempo de espera lanza una TaskCanceledException con un TimeoutException en su interior. En versiones anteriores de .NET Core, la excepción interna no existe. En .NET Framework, obtendrá HttpRequestException en su lugar. Para obtener más información, consulte HttpClient.Timeout y Realizar solicitudes HTTP con la clase HttpClient.
También TaskCanceledException significa que alguien canceló la solicitud, por ejemplo, un usuario que cerró la página. Para distinguir un tiempo de espera de una cancelación, comprueba ex.InnerException is TimeoutException o comprueba si tu propio token ha sido cancelado.
El controlador de resiliencia estándar
AddStandardResilienceHandler() combina un limitador de velocidad, un tiempo de espera total, un reintento, un cortacircuitos y un tiempo de espera por intento. Cuando un intento tarda más de 10 segundos, el tiempo de espera del intento lo cancela y la estrategia de reintento vuelve a intentarlo: hasta 3 reintentos, con retroceso exponencial y variación aleatoria, a partir de 2 segundos. Cuando la solicitud completa, incluidos los reintentos, tarda más de 30 segundos, el tiempo de espera total la cancela y tu código recibe un TimeoutRejectedException.
TimeoutRejectedException deriva de Exception. No es ni un TimeoutException ni un HttpRequestException, por lo que un bloque catch (HttpRequestException) no lo captura. Para obtener la lista completa de valores predeterminados, consulte Valores predeterminados del controlador de resistencia estándar.
Por ejemplo, cuando una aplicación de .NET 10 que utiliza los valores predeterminados del controlador estándar llama a una API que tarda entre 11 y 15 segundos por respuesta, la aplicación recibe un TimeoutRejectedException tras 30 segundos y su bloque catch (HttpRequestException) no se ejecuta.
Cómo controlar los tiempos de espera de HttpClient
- Captura ambas excepciones donde llames a la API. Si utilizas el controlador estándar, captura
TimeoutRejectedException. CapturaTaskCanceledExceptionparaHttpClient.Timeout. - Distingue entre un tiempo de espera y una cancelación. Solo trata un
TaskCanceledExceptioncomo un tiempo de espera cuando su excepción interna sea unTimeoutException. Cuando el llamante haya cancelado, detente sin generar avisos. - Elija los tiempos de espera que se ajusten a la API. Si la API suele tardar más de 10 segundos, cambia el tiempo de espera por intento y el tiempo de espera total en
AddStandardResilienceHandler(options => ...). - No reintentes
POSTniPATCHa menos que la API lo permita de forma segura. El controlador estándar vuelve a intentar todos los métodos de forma predeterminada, incluidoPOST. Llama aoptions.Retry.DisableForUnsafeHttpMethods()para excluirPOST,PATCH,PUT,DELETEyCONNECT, o aoptions.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch)para seguir reintentandoPUTeDELETE, que son idempotentes. - Indique al usuario qué ha ocurrido. Muestra "el servicio es lento, inténtelo de nuevo" en lugar de un error 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;
}
}
Cómo probar que la aplicación controla los tiempos de espera
Rara vez se produce un tiempo de espera durante el desarrollo, por lo que los bloques catch rara vez se ejecutan. La forma de probar decide si encuentras los errores antes de que los usuarios lo hagan.
| Approach | Lo que encuentras | Lo que te pierdes |
|---|---|---|
| A la espera de la producción | Tiempos de espera reales | Todo, hasta que un usuario lo pulse |
| Simula la API en tus pruebas o deja que tu agente de programación escriba la simulación | Independientemente de si se ejecuta el bloque catch, si la simulación lanza la excepción correcta |
Tu configuración real de HttpClient, los reintentos del gestor de resiliencia y la excepción que lanza realmente. La aplicación también necesita un interruptor solo para pruebas para llegar al mock. |
| Llame a la API real y espere que sea lenta. | Comportamiento real | No puedes ralentizar la API a voluntad |
| Intercepta el tráfico real de tu aplicación y retrasa las respuestas | Tu HttpClient real, el gestor de resiliencia y las excepciones |
Nada en la aplicación cambia, por lo que no prueba el código de forma aislada. Reserve las pruebas unitarias para eso. |
Pruébalo en tu app
Dev Proxy intercepta las solicitudes de la aplicación a la API y retrasa las respuestas con LatencyPlugin. .NET usa el proxy del sistema, por lo que no es necesario cambiar el código. En este ejemplo se retrasa cada respuesta de 11 a 15 segundos, más que el tiempo de espera de intento de 10 segundos del controlador estándar. Reemplace https://api.contoso.com por la dirección URL de la API a la que llama la aplicación.
Archivo: 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
}
}
Inicie el proxy de desarrollo con devproxy --config-file devproxyrc.json y ejecute la aplicación. Cada intento agota el tiempo de espera, el controlador vuelve a intentarlo y, tras 30 segundos, tu código recibe un TimeoutRejectedException. Para probar HttpClient.Timeout en su lugar, establece minMs en un valor superior al tiempo de espera que hayas configurado. Para obtener detalles sobre la configuración, consulta Usar Dev Proxy con aplicaciones .NET. Para instalar Dev Proxy, consulte Configurar Dev Proxy.