Use um único CancellationToken para cabeçalhos e corpo do .NET HttpClient
Leve o CancellationToken da requisição tanto para a preparação da resposta do HttpClient quanto para a leitura do corpo, em vez de cronometrar apenas a espera de quem chama.
Um cronômetro pode parar de esperar enquanto a requisição HTTP prossegue. Entregue o cancelamento à própria operação quando uma tentativa via proxy tem um orçamento de tempo definido.
Para uma aplicação .NET moderna com um HttpClient configurado para proxy já existente:
using var budget = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var response = await client.GetAsync(
targetUrl,
HttpCompletionOption.ResponseHeadersRead,
budget.Token);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync(budget.Token);
O mesmo orçamento começa antes da preparação e continua válido durante a leitura do corpo. O ResponseHeadersRead devolve o controle após os cabeçalhos, então o timeout próprio do HttpClient não define sozinho o orçamento da leitura posterior do corpo.
Mantenha o cancelamento separado do status HTTP e dos erros de transporte. Uma OperationCanceledException pode vir do token fornecido ou de outro timeout configurado; verifique a origem de cancelamento aplicável em vez de relatar toda falha como proxy morto.
Reutilize o cliente que é dono desta rota de proxy. Cancelar uma tentativa não significa reconstruir todo o pool de conexões para cada requisição seguinte.
O exemplo armazena em buffer um corpo de resposta de tamanho finito. Um download em streaming deve passar o token pelo laço de cópia do stream e liberar a resposta em todo caminho de saída.
O cancelamento expressa uma política da operação, não uma promessa exata de tempo de relógio para cada ação da plataforma. Evite trocá-lo por um laço infinito de verificação em Task.IsCompleted.