.NET HttpClient-időtúllépések: TaskCanceledException és TimeoutRejectedException

Ha egy API túl lassú, a .NET-alkalmazás 2 kivételből 1-et kap, attól függően, hogy melyik időtúllépés aktiválódott. HttpClient.Timeout kivált egy TaskCanceledException. A standard rugalmassági kezelő Microsoft.Extensions.Http.Resilience dob a Pollyból TimeoutRejectedException. Különböző helyekről származnak, különböző alapértelmezett értékekkel rendelkeznek, és külön catch blokkokra van szükségük.

Melyik időtúllépés aktiválódott

Időkorlát Alapértelmezett Amit a kódod kap A hely, ahol beállítja
HttpClient.Timeout 100 másodperc TaskCanceledException, amelyben a(z) TimeoutException a(z) InnerException szerepét tölti be (.NET 5 és újabb) HttpClient.Timeout
A standard kezelő kísérletének időtúllépése Kísérletenként 10 másodperc Először semmi: a kezelőprogram újra megpróbálja a kísérletet AddStandardResilienceHandler(options => ...)
Standard kezelőprogram teljes időtúllépése 30 másodperc, az újrapróbálkozásokat is beleértve Polly.Timeout.TimeoutRejectedException AddStandardResilienceHandler(options => ...)

HttpClient.Timeout

HttpClient.Timeout minden olyan kérésre vonatkozik, amelyet a HttpClient példány küld. Ha egy kéréshez eltérő időtúllépést szeretne használni, adjon át egy CancellationToken-t egy saját időtúllépéssel rendelkező CancellationTokenSource-ből. A kettő közül a rövidebb érvényes. A kikapcsoláshoz állítsa a(z) Timeout.InfiniteTimeSpan elemet erre:

Az .NET 5-ös és újabb verzióiban az időtúllépés egy TimeoutException belső kivétellel rendelkező TaskCanceledException kivételt dob. A .NET Core korábbi verzióiban a belső kivétel nem létezik. A .NET-keretrendszerben ehelyett HttpRequestException kap. További információ: HttpClient.Timeout és HTTP-kérések végrehajtása a HttpClient osztállyal.

A TaskCanceledException azt is jelenti, hogy valaki visszavonta a kérelmet, például egy felhasználó, aki bezárta az oldalt. Ha meg szeretné különböztetni az időtúllépést a megszakítástól, ellenőrizze a ex.InnerException is TimeoutException elemet, vagy ellenőrizze, hogy a saját tokenje meg van-e szakítva.

A standard rezilienciakezelő

AddStandardResilienceHandler() egy sebességkorlátozót, egy teljes időtúllépést, egy újrapróbálkozást, egy áramkör-megszakítót és egy kísérletenkénti időtúllépést fűz össze. Ha egy kísérlet több mint 10 másodpercet vesz igénybe, a kísérlet időtúllépése megszakítja azt, és az újrapróbálkozási stratégia újra próbálkozik: legfeljebb 3 újrapróbálkozás, exponenciális visszalépéssel és jitterrel, 2 másodperces kezdő értékkel. Ha a teljes kérelem, beleértve az újrapróbálkozásokat is, több mint 30 másodpercet vesz igénybe, a teljes időtúllépés megszakítja azt, és a kódod egy TimeoutRejectedException kap.

TimeoutRejectedException Exception-ből származik. Ez sem egy TimeoutException, sem egy HttpRequestException, így egy catch (HttpRequestException) blokk nem fogja meg. Az alapértelmezett értékek teljes listáját a Standard reziliencia-kezelő alapértelmezései című témakörben találja.

Ha például egy .NET 10-es alkalmazás, amely a standard kezelő alapértelmezett beállítását használja, olyan API-t hív meg, amelynél egy-egy válasz 11–15 másodpercet vesz igénybe, az alkalmazás 30 másodpercet követően kap egy TimeoutRejectedException, és a catch (HttpRequestException) blokk nem fut.

HttpClient-időtúllépések kezelése

  1. Kezelje mindkét kivételt ott, ahol meghívja az API-t. Ha a standard kezelőt használja, kapja el a TimeoutRejectedException. Elkap TaskCanceledException a következőhöz: HttpClient.Timeout.
  2. Különböztesse meg az időtúllépést a lemondástól. Csak akkor kezelje időtúllépésként TaskCanceledException , ha a belső kivétele egy TimeoutException. Amikor a hívó megszakította, állítsa le csendben.
  3. Az API-nak megfelelő időtúllépések kiválasztása. Ha az API gyakran 10 másodpercnél tovább tart, módosítsa a kísérlethez és a teljes időtúllépéshez tartozó időkorlátokat a következőben: AddStandardResilienceHandler(options => ...)
  4. Ne próbálja újra sem a POST-t, sem a PATCH-t, hacsak az API nem teszi biztonságossá. A standard kezelő alapértelmezés szerint minden metódust újrapróbál, beleértve a(z) POST metódust is. A(z) DELETE meghívásával kizárhatja a(z) POST, PUT, PATCH, options.Retry.DisableForUnsafeHttpMethods() és PUT elemeket, vagy a(z) DELETE használatával továbbra is újrapróbálhatja az idempotens CONNECT és options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch) elemeket.
  5. Mondja el a felhasználónak, mi történt. Az általános hiba helyett jelenítse meg ezt: „a szolgáltatás lassú, próbálkozzon újra”.
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;
    }
}

Annak tesztelése, hogy az alkalmazás kezeli-e az időtúllépéseket

Fejlesztés közben ritkán lát időtúllépést, így a catch blokkok ritkán futnak. Az, ahogyan tesztelsz, határozza meg, hogy a felhasználóid előtt találod-e meg a hibákat.

Approach Mit talál? Amit elmulaszt
Várjon az élesítésig Valós idejű időkorlátok Minden addig, amíg egy felhasználó rá nem kattint
Mockolja az API-t a tesztekben, vagy hagyja, hogy a kódoló asszisztens megírja a mockot Fut-e a catch blokk, ha a mock a megfelelő kivételt dobja A valódi HttpClient beállítás, a reziliencia-kezelő újrapróbálkozásai és a kivétel, amelyet ténylegesen dob. Az alkalmazásnak egy csak tesztelési kapcsolóra is szüksége van a mock eléréséhez.
Hívja meg a valódi API-t, és remélje, hogy lassú Valós viselkedés Az API-t nem lehet szándékosan lassúvá tenni
Fogd el az alkalmazás valós forgalmát, és késleltesd a válaszokat Az Ön valódi HttpClient, rezilienciakezelője és kivételei Az alkalmazásban semmi sem változik, így nem teszteli a kódot elkülönítve. Arra valók az egységtesztek.

Próbálja ki a saját alkalmazásában

A Dev Proxy elfogja az alkalmazás api-nak küldött kéréseit, és késlelteti a válaszokat a LatencyPlugin használatával. .NET a rendszerproxyt használja, így nem kell módosítania a kódot. Ez a példa minden választ 11–15 másodperccel késleltet, ami hosszabb, mint a standard kezelő 10 másodperces próbálkozási időkorlátja. Cserélje le a(z) https://api.contoso.com elemet az alkalmazás által hívott API URL-címére.

Fájl: 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
  }
}

Indítsa el a Dev Proxyt a(z) devproxy --config-file devproxyrc.json paranccsal, majd futtassa az alkalmazását. Minden kísérlet túllépi az időkorlátot, a kezelőprogram újrapróbálkozik, és 30 másodperc múlva a kód kap egy TimeoutRejectedException. Ehelyett állítsa a(z) minMs értékét a konfigurált időtúllépésnél magasabbra a(z) HttpClient.Timeout teszteléséhez. A beállítás részleteiért lásd: Dev Proxy használata .NET alkalmazásokkal. A Dev Proxy telepítéséhez lásd: Dev Proxy beállítása.

További lépések

Lásd még