Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Quando un'API è troppo lenta, l'app .NET ottiene 1 di 2 eccezioni, a seconda di quale timeout è scattato.
HttpClient.Timeout genera un'eccezione TaskCanceledException. Il gestore della resilienza standard di Microsoft.Extensions.Http.Resilience genera un oggetto TimeoutRejectedException da Polly. Provengono da luoghi diversi, hanno impostazioni predefinite diverse e necessitano di blocchi separati catch.
Quale timeout si è attivato?
| Interruzione temporanea | Impostazione predefinita | Cosa ottiene il tuo codice | Dove l'hai impostato |
|---|---|---|---|
HttpClient.Timeout |
100 secondi |
TaskCanceledException, con TimeoutException come InnerException (.NET 5 e versioni successive) |
HttpClient.Timeout |
| Timeout del tentativo del gestore standard | 10 secondi per tentativo | All'inizio non accade nulla: il gestore ripete il tentativo | AddStandardResilienceHandler(options => ...) |
| Timeout totale del gestore standard | 30 secondi, inclusi tutti i tentativi | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout si applica a ogni richiesta inviata dall'istanza HttpClient . Per usare un timeout diverso per una richiesta, passare un CancellationToken da un CancellationTokenSource con il proprio timeout. Si applica il più breve dei due. Impostare Timeout.InfiniteTimeSpan per disattivarlo.
In .NET 5 e versioni successive, un timeout genera un TaskCanceledException con un TimeoutException all'interno. Nelle versioni precedenti di .NET Core l'eccezione interna non è presente. In .NET Framework si ottiene invece un oggetto HttpRequestException . Per informazioni dettagliate, vedere HttpClient.Timeout e Effettuare richieste HTTP con la classe HttpClient.
Un TaskCanceledException significa anche che un utente ha annullato la richiesta, ad esempio un utente che ha chiuso la pagina. Per distinguere un timeout da un annullamento, controllare ex.InnerException is TimeoutException oppure verificare se il proprio token è annullato.
Gestore della resilienza standard
AddStandardResilienceHandler() concatena un limitatore di velocità, un timeout totale, un nuovo tentativo, un circuit breaker e un timeout di tentativo. Quando un tentativo richiede più di 10 secondi, il timeout del tentativo lo annulla e la strategia di ripetizione dei tentativi tenta di nuovo: fino a 3 tentativi, con backoff esponenziale e jitter, a partire da 2 secondi. Quando l'intera richiesta, inclusi i tentativi, richiede più di 30 secondi, il timeout totale la annulla e il codice ottiene un TimeoutRejectedException.
TimeoutRejectedException deriva da Exception. Non è né un oggetto TimeoutException né un HttpRequestExceptionoggetto , quindi un catch (HttpRequestException) blocco non lo intercetta. Per l'elenco completo delle impostazioni predefinite, vedere Valori predefiniti del gestore di resilienza standard.
Ad esempio, quando un'app .NET 10 che usa le impostazioni predefinite del gestore standard chiama un'API che richiede da 11 a 15 secondi per risposta, l'app ottiene un valore TimeoutRejectedException dopo 30 secondi e il relativo catch (HttpRequestException) blocco non viene eseguito.
Come gestire i timeout di HttpClient
- Intercettare entrambe le eccezioni in cui si chiama l'API. Se si usa il gestore standard, intercettare
TimeoutRejectedException. CatchTaskCanceledExceptionperHttpClient.Timeout. - Distinguere un timeout da un annullamento. Considerare un oggetto
TaskCanceledExceptioncome timeout solo quando l'eccezione interna è un oggettoTimeoutException. Quando il chiamante ha annullato, fermarsi in modo silenzioso. - Scegli timeout adatti all'API. Se l'API richiede spesso più di 10 secondi, modificare il tentativo e i timeout totali in
AddStandardResilienceHandler(options => ...). - Non riprovare
POSToPATCHa meno che l'API non garantisca che sia sicuro farlo. Il gestore standard ritenta tutti i metodi per impostazione predefinita, inclusoPOST. Chiamareoptions.Retry.DisableForUnsafeHttpMethods()per escluderePOST,PATCH,PUT,DELETE, eCONNECT, ooptions.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch)per continuare a ripetere idempotentiPUTeDELETE. - Indicare all'utente cosa è successo. Mostra "il servizio è lento, riprovare" anziché un errore generico.
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;
}
}
Come testare che l'app gestisca i timeout
Raramente viene visualizzato un timeout durante lo sviluppo, quindi i catch blocchi vengono eseguiti raramente. Il modo in cui si testa decide se trovi i bug prima che li trovino gli utenti.
| Avvicinarsi | Cosa trovi | Quello che ti manca |
|---|---|---|
| Attendere l'ambiente di produzione | Timeout in tempo reale | Tutto, fino a quando un utente non ci clicca |
| Simula l'API nei test oppure lascia che il tuo agente di coding scriva il mock | Se il catch blocco viene eseguito, se la simulazione genera l'eccezione corretta |
La configurazione reale HttpClient, i tentativi del gestore di resilienza e l'eccezione che viene effettivamente generata. L'app richiede anche un'opzione di sola prova per raggiungere il mock. |
| Chiamare l'API reale e sperare che sia lenta | Comportamento reale | Non puoi rendere lenta l'API su richiesta |
| Intercettare il traffico reale dell'app e ritardare le risposte | Il gestore reale HttpClient, il gestore della resilienza e le eccezioni |
Niente nella tua app cambia, quindi non testa il codice in isolamento. Mantieni i test unitari per quello. |
Provalo nella tua app
Dev Proxy intercetta le richieste dell'app all'API e ritarda le risposte con LatencyPlugin. .NET usa il proxy di sistema, quindi non è necessario modificare il codice. Questo esempio ritarda ogni risposta da 11 a 15 secondi, più a lungo del timeout di tentativo di 10 secondi del gestore standard. Sostituire https://api.contoso.com con l'URL dell'API chiamata dall'app.
File: 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
}
}
Avviare Dev Proxy con devproxy --config-file devproxyrc.json ed eseguire l'app. Ogni tentativo raggiunge il timeout, il gestore ritenta e dopo 30 secondi il codice riceve TimeoutRejectedException. Per eseguire il test HttpClient.Timeout , impostare minMs invece un valore superiore al timeout configurato. Per informazioni dettagliate sull'installazione, vedere Usare Dev Proxy con applicazioni .NET. Per installare Dev Proxy, vedere Configurare Dev Proxy.