Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Wenn eine API zu langsam ist, erhält Ihre .NET App 1 von 2 Ausnahmen, je nachdem, welches Timeout ausgelöst wurde.
HttpClient.Timeout wirft ein TaskCanceledException. Der Standard-Resilience-Handler von Microsoft.Extensions.Http.Resilience löst eine Ausnahme TimeoutRejectedException von Polly aus. Sie stammen von verschiedenen Orten, haben unterschiedliche Standardwerte und benötigen separate catch Blöcke.
Welcher Timeout wurde ausgelöst?
| Timeout | Vorgabe | Was Ihr Code erhält | Wo Sie es festlegen |
|---|---|---|---|
HttpClient.Timeout |
100 Sekunden |
TaskCanceledException, mit einem TimeoutException als seinem InnerException (.NET 5 und höher) |
HttpClient.Timeout |
| Standard-Handler-Versuchs-Timeout | 10 Sekunden pro Versuch | Zunächst nichts: Der Handler wiederholt den Versuch. | AddStandardResilienceHandler(options => ...) |
| Gesamt-Timeout des Standardhandlers | 30 Sekunden, einschließlich aller Wiederholungen | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout gilt für jede Anfrage, die die HttpClient Instanz sendet. Wenn Sie ein anderes Timeout für eine Anforderung verwenden möchten, übergeben Sie einen CancellationToken von einem CancellationTokenSource mit einem eigenen Timeout. Die kürzere der beiden gilt. Stellen Sie Timeout.InfiniteTimeSpan ein, um es zu deaktivieren.
Bei .NET 5 und höher löst ein Timeout ein TaskCanceledException mit einem TimeoutException darin aus. In früheren Versionen von .NET Core ist die innere Ausnahme nicht vorhanden. Auf .NET Framework erhalten Sie stattdessen ein HttpRequestException. Ausführliche Informationen finden Sie unter HttpClient.Timeout und HTTP-Anforderungen mit der HttpClient-Klasse senden.
Ein TaskCanceledException bedeutet auch, dass jemand die Anforderung abgebrochen hat, z. B. ein Benutzer, der die Seite geschlossen hat. Um ein Timeout von einem Abbruch zu unterscheiden, überprüfen Sie ex.InnerException is TimeoutException, oder überprüfen Sie, ob Ihr eigenes Token abgebrochen wurde.
Der Standardresilienzhandler
AddStandardResilienceHandler() verkettet einen Ratenbegrenzer, einen Gesamttimeout, einen Wiederholungsversuch, einen Circuit Breaker und ein Versuchs-Timeout. Wenn ein Versuch länger als 10 Sekunden dauert, bricht das Attempt-Timeout den Versuch ab, und die Wiederholungsstrategie versucht es erneut: bis zu 3 Wiederholungen, mit exponentiellem Backoff und Jitter ab 2 Sekunden. Wenn die gesamte Anforderung, einschließlich Wiederholungen, länger als 30 Sekunden dauert, bricht das Gesamttimeout sie ab, und Ihr Code erhält eine TimeoutRejectedException.
TimeoutRejectedException wird von Exception abgeleitet. Es handelt sich weder um ein TimeoutException noch um ein HttpRequestException, sodass ein catch (HttpRequestException) Block dies nicht abfangen kann. Die vollständige Liste der Standardwerte finden Sie unter Standardwerte des Resilienzhandlers.
Wenn beispielsweise eine .NET 10-App, die die Standardhandlerstandardeinstellungen verwendet, eine API aufruft, die 11 bis 15 Sekunden pro Antwort benötigt, erhält die App nach 30 Sekunden eine TimeoutRejectedException, und ihr catch (HttpRequestException) Block wird nicht ausgeführt.
Behandeln von HttpClient-Timeouts
- Erfassen Sie beide Ausnahmen, wenn Sie die API aufrufen. Wenn Sie den Standardhandler verwenden, fangen Sie
TimeoutRejectedExceptionab. ErhalteTaskCanceledExceptionfürHttpClient.Timeout. - Unterscheiden Sie ein Timeout von einem Abbruch. Behandeln Sie ein
TaskCanceledExceptionTimeout nur als Timeout, wenn es sich bei seiner inneren Ausnahme um eineTimeoutException. Wenn der Anrufer abgebrochen hat, beenden Sie den Vorgang stillschweigend. - Wählen Sie Timeouts aus, die der API entsprechen. Wenn die API häufig länger als 10 Sekunden benötigt, ändern Sie den Versuch und die Gesamttimeouts in
AddStandardResilienceHandler(options => ...). - Versuchen Sie es nicht mit
POSToderPATCH, es sei denn, die API stuft dies als sicher ein. Der Standardhandler versucht standardmäßig alle Methoden erneut, einschließlichPOST. Rufen Sieoptions.Retry.DisableForUnsafeHttpMethods()auf, umPATCH,options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch),PUT,CONNECTundDELETEauszuschließen, oderPOST, um idempotentePUTundDELETEweiter zu wiederholen. - Teilen Sie dem Benutzer mit, was passiert ist. Zeigen Sie "Der Dienst ist langsam. Versuchen Sie es erneut." anstelle eines generischen Fehlers an.
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;
}
}
So testen Sie, dass Ihre App Timeouts verarbeitet
Wenn Sie entwickeln, wird selten ein Timeout angezeigt, sodass Ihre catch Blöcke selten ausgeführt werden. Die Art und Weise, wie Sie testen, entscheidet, ob Sie die Fehler finden, bevor Ihre Benutzer dies tun.
| Approach | Was Sie finden | Was Ihnen fehlt |
|---|---|---|
| Warten auf die Produktion | Zeitüberschreitungen in Echtzeit | Alles, bis ein Benutzer darauf klickt |
| Mocken Sie die API in Ihren Tests, oder lassen Sie Ihren Coding-Agenten den Mock schreiben | Ob Ihr catch-Block ausgeführt wird, wenn das Mock die richtige Ausnahme auslöst |
Ihr tatsächliches HttpClient Setup, die Wiederholungen des Resilienzhandlers und die Ausnahme, die er tatsächlich auslöst. Ihre App benötigt außerdem einen reinen Testschalter, um den Mock zu erreichen. |
| Rufen Sie die echte API auf, und hoffen Sie, dass sie langsam ist. | Tatsächliches Verhalten | Sie können die API nicht absichtlich verlangsamen |
| Fangen Sie den tatsächlichen Datenverkehr Ihrer App ab und verzögern Sie die Antworten | Ihr echter HttpClient, Resilienz-Handler und Ihre Ausnahmen |
Nichts in Ihrer App ändert sich, sodass Ihr Code nicht isoliert getestet wird. Halten Sie die Unit-Tests dafür. |
Probieren Sie es in Ihrer App aus
Dev Proxy fängt die Anforderungen Ihrer App an die API ab und verzögert die Antworten mit dem LatencyPlugin. .NET verwendet den Systemproxy, sodass Sie Ihren Code nicht ändern müssen. In diesem Beispiel wird jede Antwort um 11 bis 15 Sekunden verzögert, länger als das 10-Sekunden-Timeout für Versuche des Standardhandlers. Ersetzen Sie https://api.contoso.com durch die URL der API, die Ihre App aufruft.
Datei: 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
}
}
Starten Sie Dev Proxy mit devproxy --config-file devproxyrc.json , und führen Sie Ihre App aus. Bei jedem Versuch kommt es zu einer Zeitüberschreitung, der Handler versucht es erneut, und nach 30 Sekunden erhält Ihr Code ein TimeoutRejectedException. Um stattdessen zu testen HttpClient.Timeout , legen Sie minMs höher als das von Ihnen konfigurierte Timeout fest. Details zur Einrichtung finden Sie in Dev Proxy mit .NET-Anwendungen verwenden. Informationen zum Installieren von Dev Proxy finden Sie unter Einrichten von Dev Proxy.