Microsoft .NET/C#
[C#] TaskCanceledException과 OperationCanceledException, HttpClient 타임아웃 구분
전자기린
2026. 11. 10. 00:33

HttpClient 요청이 타임아웃됐는데 로그에는 "작업이 취소되었습니다"로만 남아 원인을 못 찾는 경우가 많습니다. 두 예외의 차이와 구분 방법입니다.
절차
- 증상 확인
- 원인: .NET 5 이후 타임아웃도 취소로 처리
- 구분: InnerException이 TimeoutException인지 확인
- 타임아웃과 사용자 취소 분리 처리
1. 증상
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at System.Net.Http.HttpClient.HandleFailure(Exception e, ...)내가 취소한 적이 없는데 취소 예외가 올라와서 서버 문제인지 코드 문제인지 헷갈립니다.
2. 원인
HttpClient의 기본 타임아웃(100초)이 지나면 내부적으로 CancellationToken을 취소시키고, 그 결과 TaskCanceledException이 올라옵니다. .NET 5부터는 InnerException에 TimeoutException이 함께 들어 있습니다.
상속 관계는 다음과 같습니다.
OperationCanceledExceptionTaskCanceledException—OperationCanceledException을 상속
따라서 catch (OperationCanceledException)만 쓰면 내 취소와 타임아웃이 함께 잡힙니다.
3. 구분 코드
try
{
var res = await client.GetAsync(url, ct);
res.EnsureSuccessStatusCode();
}
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException)
{
// HttpClient 타임아웃
logger.LogWarning("요청 타임아웃: {Url}", url);
}
catch (OperationCanceledException)
{
// 사용자가 취소한 경우(ct)
}
catch (HttpRequestException ex)
{
// 연결 실패, DNS, 인증서 오류 등
}4. 타임아웃 시간 지정
기본 100초는 대부분 너무 깁니다. 요청 단위로 줄이는 편이 낫습니다.
client.Timeout = TimeSpan.FromSeconds(10);
// 요청마다 적용
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(10));
var res = await client.GetAsync(url, cts.Token);전역 설정은 IHttpClientFactory 등록에서 합니다.
builder.Services.AddHttpClient("api", c =>
{
c.BaseAddress = new Uri("https://api.example.com");
c.Timeout = TimeSpan.FromSeconds(10);
});정리
- 타임아웃도
TaskCanceledException으로 온다 InnerException is TimeoutException으로 구분한다- 기본 100초를 그대로 두지 않는다