Como posso saber quando o HttpClient expirou?

142

Tanto quanto posso dizer, não há como saber que ocorreu especificamente um tempo limite. Não estou procurando o lugar certo ou estou perdendo algo maior?

string baseAddress = "http://localhost:8080/";
var client = new HttpClient() 
{ 
    BaseAddress = new Uri(baseAddress), 
    Timeout = TimeSpan.FromMilliseconds(1) 
};
try
{
    var s = client.GetAsync("").Result;
}
catch(Exception e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(e.InnerException.Message);
}

Isso retorna:

Ocorreu um ou mais erros.

Uma tarefa foi cancelada.

Benjol
fonte
3
Podemos upvote a questão no GitHub: HttpClient joga TaskCanceledException no tempo limite # 20296
csrowell
Upvote enorme para a pergunta. Além disso ... alguma idéia de como fazer isso na UWP? Seu Windows.Web.HTTP.HTTPClient não tem membro de tempo limite. O método GetAsync também não aceita o token de cancelamento ...
Do-do-new
1
Seis anos depois, e ainda não parece possível saber se um cliente expirou.
Steve Smith

Respostas:

61

Você precisa aguardar o GetAsyncmétodo. Em seguida, ele lançará um TaskCanceledExceptionse tiver atingido o tempo limite. Além disso, GetStringAsynce GetStreamAsynclida internamente com o tempo limite, para que NUNCA joguem.

string baseAddress = "http://localhost:8080/";
var client = new HttpClient() 
{ 
    BaseAddress = new Uri(baseAddress), 
    Timeout = TimeSpan.FromMilliseconds(1) 
};
try
{
    var s = await client.GetAsync();
}
catch(Exception e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(e.InnerException.Message);
}
murkaeus
fonte
2
Eu testei isso e GetStreamAsyncjoguei um TaskCanceledExceptionpara mim.
Sam
38
Como posso saber se isso TaskCanceledExceptioné causado pelo tempo limite do HTTP e não, como cancelamento direto ou outro motivo?
UserControl
8
Verificação @UserControl TaskCanceledException.CancellationToken.IsCancellationRequested. Se falso, você pode estar razoavelmente certo de que foi um tempo limite.
28814 Todd Menier
3
Como se vê, você não pode contar com IsCancellationRequesteda configuração do token da exceção no cancelamento direto, como eu pensava anteriormente: stackoverflow.com/q/29319086/62600
Todd Menier
2
@testing Eles não se comportam de maneira diferente. Só que você tem um token que representará a solicitação de cancelamento do usuário e um interno (você não pode acessar e não precisa) que representa o tempo limite do cliente. É o caso de uso que difere
Sir Rufo
59

Estou reproduzindo o mesmo problema e é realmente irritante. Eu achei estes úteis:

HttpClient - lidando com exceções agregadas

O bug no HttpClient.GetAsync deve gerar WebException, não TaskCanceledException

Algum código no caso de os links não chegarem a lugar algum:

var c = new HttpClient();
c.Timeout = TimeSpan.FromMilliseconds(10);
var cts = new CancellationTokenSource();
try
{
    var x = await c.GetAsync("http://linqpad.net", cts.Token);  
}
catch(WebException ex)
{
    // handle web exception
}
catch(TaskCanceledException ex)
{
    if(ex.CancellationToken == cts.Token)
    {
        // a real cancellation, triggered by the caller
    }
    else
    {
        // a web request timeout (possibly other things!?)
    }
}
vezenkov
fonte
Na minha experiência, o WebException não pode ser capturado em nenhuma circunstância. Os outros estão experimentando algo diferente?
esmagar
1
@crush WebException pode ser capturado. Talvez isso ajude.
DavidRR
Isso não funciona para mim se eu não estiver usando cts. Estou apenas usando a tarefa <T> task = SomeTask (), tente {T result = task.Result} catch (TaskCanceledException) {} catch (Exception e) {} Somente a exceção geral é capturada, não a TaskCanceledException. O que há de errado na minha versão do código?
Naomi
1
Criei um novo relatório de bug, já que o original parece estar em uma postagem arquivada no fórum: connect.microsoft.com/VisualStudio/feedback/details/3141135
StriplingWarrior 15/17
1
Se o token é passado pela verificação externa, não é default(CancellationToken)antes de comparar com ex.CancellationToken.
SerG 18/06/19
25

Descobri que a melhor maneira de determinar se a chamada de serviço expirou é usar um token de cancelamento e não a propriedade timeout do HttpClient:

var cts = new CancellationTokenSource();
cts.CancelAfter(timeout);

E então lide com o CancellationException durante a chamada de serviço ...

catch(TaskCanceledException)
{
    if(!cts.Token.IsCancellationRequested)
    {
        // Timed Out
    }
    else
    {
        // Cancelled for some other reason
    }
}

Obviamente, se o tempo limite ocorrer no lado do serviço, isso poderá ser tratado por uma WebException.

Jack
fonte
1
Hmm, acho que o operador de negação (que foi adicionado em uma edição) deve ser removido para que esta amostra faça sentido? Se cts.Token.IsCancellationRequestedé trueisso deve significar que um tempo limite ocorreu?
Lasse Christiansen
9

Em http://msdn.microsoft.com/en-us/library/system.net.http.httpclient.timeout.aspx

Uma consulta ao sistema de nomes de domínio (DNS) pode levar até 15 segundos para retornar ou atingir o tempo limite. Se sua solicitação contiver um nome de host que exija resolução e você definir o Tempo limite para um valor inferior a 15 segundos, poderá levar 15 segundos ou mais antes que uma WebException seja lançada para indicar um tempo limite na sua solicitação .

Você obtém acesso à Statuspropriedade, consulte WebExceptionStatus

user247702
fonte
3
Estou voltando e AggregateExceptioncom um TaskCancelledExceptioninterior. Eu devo estar fazendo algo errado ...
Benjol
Você está usando catch(WebException e)?
User247702
Não, e se eu tentar, o AggregateExceptioné não tratado. Se você criar um projeto de console do VS, adicionar uma referência System.Net.Httpe soltar o código main, poderá ver por si mesmo (se desejar).
Benjol
5
Se o período de espera exceder o tempo limite da tarefa, você receberá um TaskCanceledException. Isso parece ser causado pelo processamento de tempo limite interno do TPL, em um nível mais alto que o HttpWebClient. Não parece haver uma boa maneira de distinguir entre um cancelamento de tempo limite e um cancelamento de usuário. O resultado disso é que você pode não ter um WebExceptioninterior dentro do seu AggregateException.
JT.
1
Como outros já disseram, você deve assumir que o TaskCanceledException foi o tempo limite. Estou usando o try {// Code here} catch (exceção AggregateException) {if (exception.InnerExceptions.OfType <TaskCanceledException> () .Any ()) {// manipular o tempo limite aqui}}
Vdex
8

Basicamente, você precisa capturar OperationCanceledExceptione verificar o estado do token de cancelamento que foi passado para SendAsync(ou GetAsync, ou qualquer outro HttpClientmétodo que esteja usando):

  • se foi cancelado ( IsCancellationRequestedé verdade), significa que a solicitação foi realmente cancelada
  • caso contrário, significa que o pedido expirou

Obviamente, isso não é muito conveniente ... seria melhor receber um TimeoutExceptionem caso de tempo limite. Eu proponho uma solução aqui com base em um manipulador de mensagens HTTP personalizado: Melhor controle de tempo limite com HttpClient

Thomas Levesque
fonte
ah! é você! Escrevi um comentário em seu blog post hoje cedo. Mas wrt a esta resposta, acho que seu ponto sobre IsCancellationRequested não é verdade, porque parece que é sempre verdadeiro para mim, quando eu não cancelá-lo eu mesmo
knocte
@knocte isso é estranho ... Mas, nesse caso, a solução do meu blog post não irá ajudá-lo, uma vez que também depende desse
Thomas Levesque
1
na questão do github sobre isso, muitos afirmam o que eu disse: que IsCancellationRequested é verdadeiro quando há um tempo limite; então eu estou tentado a downvote sua resposta;)
knocte
@ Knocte, eu não sei o que te dizer ... Eu tenho usado isso por um longo tempo e sempre funcionou para mim. Você definiu o HttpClient.Timeoutinfinito?
Thomas Levesque
não, eu não fiz porque eu não posso controlar HttpClient mim, é uma biblioteca thirdparty que eu estou usando, aquele que usa-lo
knocte
-1
_httpClient = new HttpClient(handler) {Timeout = TimeSpan.FromSeconds(5)};

é o que eu costumo fazer, parece funcionar muito bem para mim, é especialmente bom ao usar proxies.

Desenvolvimento Syv
fonte
1
É assim que você configura o tempo limite do httpclient. Isso não aborda a questão, que era como você informa quando o httpclient atinge o tempo limite.
Ethan Fischer