API가 너무 느리면 .NET 앱에서는 어떤 타임아웃이 트리거되었는지에 따라 2가지 예외 중 하나가 발생합니다.
HttpClient.Timeout이 TaskCanceledException을 발생시킵니다.
Microsoft.Extensions.Http.Resilience의 표준 복원력 처리기는 Polly의 TimeoutRejectedException를 throw합니다. 그들은 다른 장소에서 온, 다른 기본값을 가지고, 별도의 catch 블록이 필요합니다.
어떤 시간 초과가 발생했는지
| Timeout | Default | 코드가 얻는 내용 | 설정하는 위치 |
|---|---|---|---|
HttpClient.Timeout |
100초 |
TaskCanceledException, InnerException(.NET 5 이상)으로 TimeoutException을(를) 사용하여 |
HttpClient.Timeout |
| 표준 처리기 시도 시간 제한 | 시도당 10초 | 처음에는 아무 일도 일어나지 않습니다: 처리기가 해당 시도를 다시 수행합니다. | AddStandardResilienceHandler(options => ...) |
| 표준 처리기 총 시간 제한 | 모든 재시도를 포함해 30초 | Polly.Timeout.TimeoutRejectedException |
AddStandardResilienceHandler(options => ...) |
HttpClient.Timeout
HttpClient.Timeout 는 HttpClient 인스턴스가 보내는 모든 요청에 적용됩니다. 한 요청에 대해 다른 시간 제한을 사용하려면 자체 시간 제한이 있는 CancellationTokenSource에서 CancellationToken를 전달합니다. 두 가지 중 더 짧은 값이 적용됩니다.
Timeout.InfiniteTimeSpan로 설정하여 끕니다.
.NET 5 이상에서는 시간 초과 시 내부에 TaskCanceledException가 있는 TimeoutException이 throw됩니다. 이전 버전에서는 .NET Core에 내부 예외가 없습니다. .NET Framework에서는 대신 HttpRequestException을(를) 얻게 됩니다. 자세한 내용은 HttpClient.Timeout 및 HttpClient 클래스를 사용하여 HTTP 요청 만들기를 참조하세요.
A TaskCanceledException 는 또한 누군가가 요청을 취소했음을 의미합니다(예: 페이지를 닫은 사용자). 시간 초과와 취소를 구분하려면 ex.InnerException is TimeoutException를 확인하거나, 자체 토큰이 취소되었는지 확인하세요.
표준 복원력 처리기
AddStandardResilienceHandler() 는 속도 제한기, 총 시간 제한, 재시도, 회로 차단기 및 시도 제한 시간을 연결합니다. 한 번의 시도가 10초보다 오래 걸리면 시도 시간 제한이 해당 시도를 취소하고 재시도 전략이 다시 시도합니다. 최대 3번 재시도하며, 2초부터 시작하는 지수 백오프 및 지터를 적용합니다. 재시도를 포함한 전체 요청이 30초보다 오래 걸리면 총 시간 제한이 요청을 취소하고 코드에서 TimeoutRejectedException를 받게 됩니다.
TimeoutRejectedException 에서 파생됩니다 Exception. 그것은 TimeoutException도 아니고 HttpRequestException도 아니므로, catch (HttpRequestException) 블록은 그것을 잡을 수 없습니다. 기본값의 전체 목록은 표준 복원력 처리기 기본값을 참조하세요.
예를 들어 표준 처리기의 기본값을 사용하는 .NET 10 앱이 응답당 11~15초가 걸리는 API를 호출하는 경우 앱은 30초 후에 TimeoutRejectedException가 발생하고 해당 catch (HttpRequestException) 블록이 실행되지 않습니다.
HttpClient 시간 제한을 처리하는 방법
- API를 호출하는 곳에서 두 예외를 모두 처리합니다. 표준 처리기를 사용하는 경우
TimeoutRejectedException를 처리합니다.HttpClient.Timeout의TaskCanceledException를 확인하세요. - 시간 제한과 취소를 구분합니다.
TaskCanceledException의 내부 예외가TimeoutException인 경우에만 시간 제한으로 처리합니다. 호출자가 취소하면 조용히 중지합니다. - API에 맞는 시간 제한을 선택합니다. API가 종종 10초보다 오래 걸리는 경우
AddStandardResilienceHandler(options => ...)에서 시도 및 총 시간 제한을 변경하세요. - API가 안전하다고 보장하는 경우가 아니면
POST또는PATCH을 다시 시도하지 마세요. 표준 처리기는 기본적으로POST를 포함하여 모든 메서드를 다시 시도합니다.PATCH를 호출하여DELETE,PUT,POST,options.Retry.DisableForUnsafeHttpMethods(),options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch)를 제외하거나,PUT를 호출하여 idempotentCONNECT및DELETE의 재시도를 계속합니다. - 사용자에게 어떤 일이 일어났는지 알리세요. 일반 오류 대신 "서비스가 느립니다. 다시 시도하세요"를 표시합니다.
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;
}
}
앱이 시간 제한을 처리하는지 테스트하는 방법
개발하는 동안 시간 초과를 거의 보지 못하므로 catch 블록은 거의 실행되지 않습니다. 테스트 방법은 사용자가 하기 전에 버그를 찾을지 여부를 결정합니다.
| Approach | 찾은 내용 | 당신이 놓친 것 |
|---|---|---|
| 프로덕션을 기다리세요 | 실시간 시간 초과 | 사용자가 클릭하기 전까지는 모든 것 |
| 테스트에서 API를 모킹하거나 코딩 에이전트가 모의 개체를 작성하도록 허용 |
catch 블록이 모의 개체가 올바른 예외를 throw하는 경우 실행되는지 여부 |
실제 HttpClient 설정, 복원력 처리기의 재시도 및 실제로 발생시키는 예외입니다. 또한 모의 환경에 도달하려면 앱에 테스트 전용 스위치가 필요합니다. |
| 실제 API를 호출하고 속도가 느리기를 바랍니다. | 실제 동작 | 원할 때 API를 의도적으로 느리게 만들 수는 없습니다. |
| 앱의 실제 트래픽을 가로채고 응답을 지연합니다. | 실제 HttpClient, 복원성 핸들러 및 예외 |
앱에서 아무것도 변경되지 않으므로 코드를 격리된 상태에서 테스트하지 않습니다. 그건 단위 테스트로 처리하세요. |
앱에서 직접 써 보기
Dev Proxy는 앱의 API로 가는 요청을 가로채고 LatencyPlugin으로 응답을 지연합니다. .NET 시스템 프록시를 사용하므로 코드를 변경할 필요가 없습니다. 다음은 각 응답을 표준 처리기의 10초 시도 시간 제한보다 긴 11~15초 동안 지연시키는 예제입니다.
https://api.contoso.com를 앱에서 호출하는 API의 URL로 바꿉니다.
파일: 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
}
}
devproxy --config-file devproxyrc.json를 사용하여 Dev Proxy를 시작하고 앱을 실행합니다. 각 시도는 시간 초과되며 처리기는 다시 시도하고, 30초 후에 코드가 TimeoutRejectedException를 받습니다. 대신 HttpClient.Timeout를 테스트하려면 minMs를 구성한 시간 제한보다 높게 설정하세요. 설정 세부 정보는 .NET 애플리케이션에서 Dev Proxy 사용을 참조하세요. Dev Proxy를 설치하려면 Dev Proxy 설정을(를) 참조하세요.
다음 단계
또한, 다음을 참조하세요.
Dev Proxy