Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Lorsqu’une API est trop lente, votre application .NET reçoit l’une des deux exceptions, selon le délai d’expiration déclenché.
HttpClient.Timeout génère une TaskCanceledException. Le gestionnaire de résilience standard lève un TimeoutRejectedException de Microsoft.Extensions.Http.Resilience Polly. Ils proviennent de différents endroits, ont des valeurs par défaut différentes et ont besoin de blocs distincts catch .
Quel timeout s’est déclenché
| Délai d'expiration | Default | Ce que votre code obtient | L’endroit où vous l’avez réglé |
|---|---|---|---|
HttpClient.Timeout |
100 secondes |
TaskCanceledException, avec un TimeoutException comme son InnerException (.NET 5 et versions ultérieures) |
HttpClient.Timeout |
| Délai d’expiration de la tentative de gestionnaire standard | 10 secondes par tentative | Rien au début : le gestionnaire réessaie | AddStandardResilienceHandler(options => ...) |
| Délai d’expiration total du gestionnaire standard | 30 secondes, y compris tous les réessais | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout s’applique à chaque requête envoyée par l’instance HttpClient . Pour utiliser un délai d’expiration différent pour une requête, transmettez un CancellationToken provenant d’un CancellationTokenSource avec son propre délai d’expiration. Le plus court des deux s’applique. Définissez cette option sur Timeout.InfiniteTimeSpan pour la désactiver.
Sur .NET 5 et versions ultérieures, un délai d’attente lève un TaskCanceledException contenant un TimeoutException. Sur les versions antérieures de .NET Core, l'exception imbriquée n'est pas là. Sur .NET Framework, vous obtenez un HttpRequestException à la place. Pour plus d’informations, consultez HttpClient.Timeout et Effectuer des requêtes HTTP avec la classe HttpClient.
Cela TaskCanceledException signifie également qu’une personne a annulé la demande, par exemple un utilisateur qui a fermé la page. Pour distinguer un délai d’expiration d’une annulation, vérifiez ex.InnerException is TimeoutException ou vérifiez si votre propre jeton est annulé.
Gestionnaire de résilience standard
AddStandardResilienceHandler() enchaîne un limiteur de débit, un délai total d’expiration, une nouvelle tentative, un coupe-circuit et un délai d’attente de tentative. Lorsqu’une tentative prend plus de 10 secondes, le délai d’expiration de la tentative l’annule et la stratégie de réessai tente à nouveau : jusqu’à 3 nouvelles tentatives, avec temporisation exponentielle et jitter, en commençant à 2 secondes. Lorsque la requête entière, y compris les nouvelles tentatives, prend plus de 30 secondes, le délai d’expiration total l’annule et votre code reçoit un TimeoutRejectedException.
TimeoutRejectedException dérive de Exception. Ce n’est ni TimeoutException ni HttpRequestException, donc un bloc catch (HttpRequestException) ne l’intercepte pas. Pour obtenir la liste complète des valeurs par défaut, consultez les valeurs par défaut du gestionnaire de résilience Standard.
Par exemple, lorsqu'une application .NET 10 qui utilise les valeurs par défaut du gestionnaire standard appelle une API qui prend 11 à 15 secondes par réponse, l'application obtient une TimeoutRejectedException après 30 secondes, et son bloc catch (HttpRequestException) ne s'exécute pas.
Guide pratique pour gérer les délais d’expiration HttpClient
- Interceptez les deux exceptions lors de l’appel à l’API. Si vous utilisez le gestionnaire standard, interceptez
TimeoutRejectedException. RegardezTaskCanceledExceptionpourHttpClient.Timeout. - Distinguez un délai d’expiration d’une annulation. Traitez uniquement un
TaskCanceledExceptioncomme un délai d’expiration lorsque son exception interne est unTimeoutException. Lorsque l’appelant a annulé, arrêtez en silence. - Sélectionnez les délais d’expiration correspondant à l’API. Si l’API met souvent plus de 10 secondes à répondre, modifiez les délais d’expiration par tentative et total dans
AddStandardResilienceHandler(options => ...). - Ne réessayez pas
POSTouPATCH, à moins que l’API ne le permette sans risque. Le gestionnaire standard réessaie toutes les méthodes par défaut, y comprisPOST. Appelezoptions.Retry.DisableForUnsafeHttpMethods()pour exclurePOST,DELETE,PATCH,PUTetCONNECT, ouoptions.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch)pour continuer à réessayer les opérations idempotentesPUTetDELETE. - Indiquez à l’utilisateur ce qui s’est passé. Affichez « le service est lent, réessayez » au lieu d’une erreur générique.
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;
}
}
Comment tester que votre application gère les délais d’expiration
Vous voyez rarement une expiration du délai pendant que vous développez, de sorte que vos blocs catch s’exécutent rarement. La façon dont vous testez détermine si vous trouvez les bogues avant que vos utilisateurs ne le fassent.
| Approach | Ce que vous trouvez | Ce que vous avez manqué |
|---|---|---|
| Attendre le passage en production | Timeouts en temps réel | Tout, jusqu’à ce qu’un utilisateur clique dessus |
| Simuler l’API dans vos tests ou laisser votre agent de codage écrire la simulation | Indique si votre bloc catch s’exécute, si la simulation lève l’exception appropriée |
Votre configuration réelle HttpClient , les nouvelles tentatives du gestionnaire de résilience et l’exception qu’il lève réellement. Votre application a également besoin d’un commutateur réservé aux tests pour atteindre le mock. |
| Appeler l’API réelle en espérant qu’elle soit lente | Comportement réel | Vous ne pouvez pas ralentir l’API intentionnellement |
| Interceptez le trafic réel de votre application et retarder les réponses | Votre vrai HttpClient, gestionnaire de résilience et exceptions |
Rien dans votre application ne change, donc il ne teste pas votre code de manière isolée. Conservez vos tests unitaires pour cela. |
Essayez-le dans votre application
Dev Proxy intercepte les demandes de votre application à l’API et retarde les réponses avec LatencyPlugin. .NET utilise le proxy système. Vous n'avez donc pas besoin de modifier votre code. Cet exemple retarde chaque réponse de 11 à 15 secondes, plus longtemps que le délai d’expiration de 10 secondes du gestionnaire standard. Remplacez https://api.contoso.com par l’URL de l’API que votre application appelle.
Fichier : 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
}
}
Démarrez le proxy de développement avec devproxy --config-file devproxyrc.json et exécutez votre application. Chaque tentative expire, le gestionnaire retente et, après 30 secondes, votre code reçoit un TimeoutRejectedException. Pour tester HttpClient.Timeout à la place, définissez minMs sur une valeur supérieure au délai d’expiration que vous avez configuré. Pour plus d’informations sur l’installation, consultez Utiliser le proxy de développement avec les applications .NET. Pour installer Dev Proxy, consultez Configurer Dev Proxy.