.NET HttpClient-time-outs: TaskCanceledException en TimeoutRejectedException

Wanneer een API te traag is, krijgt uw .NET-app 1 van 2 excepties, afhankelijk van welke time-out is geactiveerd. HttpClient.Timeout gooit een TaskCanceledException. De standaard-resilience-handler van Microsoft.Extensions.Http.Resilience gooit een TimeoutRejectedException van Polly. Ze zijn afkomstig van verschillende plaatsen, hebben verschillende standaardwaarden en hebben afzonderlijke catch blokken nodig.

Welke time-out is geactiveerd

Onderbreking Default Wat je code krijgt Waar u het instelt
HttpClient.Timeout 100 seconden TaskCanceledException, met een TimeoutException als InnerException (.NET 5 en hoger) HttpClient.Timeout
Time-out voor standaardhandlerpoging 10 seconden per poging In eerste instantie gebeurt er niets: de handler probeert het opnieuw AddStandardResilienceHandler(options => ...)
Totale time-out van standaardhandler 30 seconden, inclusief alle herhalingspogingen Polly.Timeout.TimeoutRejectedException AddStandardResilienceHandler(options => ...)

HttpClient.Timeout

HttpClient.Timeout is van toepassing op elk verzoek dat door de HttpClient instantie wordt verzonden. Als u voor één aanvraag een andere time-out wilt gebruiken, geeft u een CancellationToken door van een CancellationTokenSource met een eigen time-out. De kortere van de twee is van toepassing. Stel Timeout.InfiniteTimeSpan in om het uit te schakelen.

Op .NET 5 en hoger gooit een time-out een TaskCanceledException met daarin een TimeoutException. In eerdere versies van .NET Core is de interne uitzondering er niet. In .NET Framework krijgt u in plaats daarvan een HttpRequestException. Zie HttpClient.Timeout en HTTP-aanvragen maken met de HttpClient-klasse voor meer informatie.

Een TaskCanceledException betekent ook dat iemand de aanvraag heeft geannuleerd, bijvoorbeeld een gebruiker die de pagina heeft gesloten. Om onderscheid te maken tussen een time-out en een annulering, controleer ex.InnerException is TimeoutException, of controleer of uw eigen token is geannuleerd.

De standaardhandler voor veerkracht

AddStandardResilienceHandler() schakelt een rate limiter, een totale time-out, een herhalingspoging, een circuit breaker en een time-out per poging aaneen. Wanneer een poging langer dan 10 seconden duurt, annuleert de time-out voor die poging deze en probeert de retrystrategie het opnieuw: tot maximaal 3 nieuwe pogingen, met exponentiële backoff en jitter, vanaf 2 seconden. Wanneer het hele verzoek, inclusief nieuwe pogingen, langer duurt dan 30 seconden, annuleert de totale time-out deze en krijgt uw code een TimeoutRejectedException.

TimeoutRejectedException is afgeleid van Exception. Het is noch een TimeoutException, noch een HttpRequestException, dus een catch (HttpRequestException)-blok vangt het niet op. Zie standaardinstellingen voor de standaardresiliencehandler voor de volledige lijst met standaardinstellingen.

Wanneer bijvoorbeeld een .NET 10-app die gebruikmaakt van de standaardinstellingen van de standaardhandler een API aanroept die 11 tot 15 seconden per antwoord nodig heeft, krijgt de app na 30 seconden een TimeoutRejectedException en wordt het catch (HttpRequestException)-blok niet uitgevoerd.

Omgaan met time-outs van HttpClient

  1. Vang beide uitzonderingen af wanneer u de API aanroept. Als u de standaardhandler gebruikt, onderschept u TimeoutRejectedException. Ontvang TaskCanceledException voor HttpClient.Timeout.
  2. Maak onderscheid tussen een time-out en een annulering. Beschouw een TaskCanceledException alleen als een time-out wanneer de interne uitzondering een TimeoutException is. Wanneer de beller annuleerde, stop stilletjes.
  3. Kies time-outs die passen bij de API. Als de API vaak langer dan 10 seconden duurt, wijzigt u de time-outs voor pogingen en totaal in AddStandardResilienceHandler(options => ...).
  4. Probeer POST of PATCH niet opnieuw, tenzij de API aangeeft dat dit veilig is. De standaardhandler voert standaard alle methoden opnieuw uit, inclusief POST. Roep options.Retry.DisableForUnsafeHttpMethods() aan om POST, PATCH, PUT, DELETE en CONNECT uit te sluiten, of options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch) om idempotente PUT en DELETE opnieuw te proberen.
  5. Vertel de gebruiker wat er is gebeurd. Toon "de service is traag, probeer het opnieuw" in plaats van een algemene foutmelding.
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;
    }
}

Testen of uw app time-outs verwerkt

U ziet zelden een time-out tijdens het ontwikkelen, dus uw catch blokken worden zelden uitgevoerd. De manier waarop u test, bepaalt of u de bugs vindt voordat uw gebruikers dat doen.

Approach Wat u vindt Wat u mist
Wacht op productie Real-time time-outs Alles, tot een gebruiker erop klikt
Mock de API in je tests, of laat je codeeragent de mock schrijven Of uw catch-blok wordt uitgevoerd en of de mock de juiste uitzondering genereert Uw echte HttpClient configuratie, de nieuwe pogingen van de resilience-handler en de uitzondering die deze daadwerkelijk genereert. Uw app heeft ook een testswitch nodig om de mock te bereiken.
Roep de echte API aan en hoop dat het traag is Feitelijk gedrag U kunt de API niet traag maken naar wens
Het echte verkeer van uw app onderscheppen en de reacties vertragen Uw echte HttpClient, resilience-handler en uitzonderingen Er verandert niets in uw app, dus uw code wordt niet geïsoleerd getest. Bewaar je unit-tests daarvoor.

Probeer het in uw app

Dev Proxy onderschept de aanvragen van uw app naar de API en vertraagt de antwoorden met de LatencyPlugin. .NET gebruikt de systeemproxy, dus u hoeft uw code niet te wijzigen. In dit voorbeeld wordt elke reactie met 11 tot 15 seconden vertraagd, langer dan de time-out voor een poging van 10 seconden van de standaardhandler. Vervang https://api.contoso.com door de URL van de API die uw app aanroept.

Bestand: 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
  }
}

Start Dev Proxy met devproxy --config-file devproxyrc.json en voer uw app uit. Er treedt een time-out op voor elke poging, de handler probeert het opnieuw en na 30 seconden krijgt uw code een TimeoutRejectedException. Om in plaats daarvan HttpClient.Timeout te testen, stelt u minMs hoger in dan de time-out die u hebt geconfigureerd. Zie Dev Proxy gebruiken met .NET toepassingen voor meer informatie over de installatie. Zie Dev Proxy instellen om Dev Proxy te installeren.

Volgende stappen 

Zie ook