Comment puis-je savoir quand HttpClient a expiré?

142

Pour autant que je sache, il n'y a aucun moyen de savoir que c'est spécifiquement un délai d'expiration qui s'est produit. Est-ce que je ne cherche pas au bon endroit ou est-ce que je manque quelque chose de plus grand?

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);
}

Cela renvoie:

Une ou plusieurs erreurs se sont produites.

Une tâche a été annulée.

Benjol
la source
3
Nous pouvons voter pour le problème sur GitHub: HttpClient lève TaskCanceledException à l'expiration du délai # 20296
csrowell
Énorme vote pour la question. Aussi ... une idée de comment faire cela sur UWP? Son Windows.Web.HTTP.HTTPClient n'a pas de membre timeout. De plus, la méthode GetAsync n'accepte pas le jeton d'annulation ...
Do-do-new
1
6 ans plus tard, et il ne semble toujours pas possible de savoir si un client a expiré.
Steve Smith

Réponses:

61

Vous devez attendre la GetAsyncméthode. Il lancera alors un TaskCanceledExceptions'il a expiré. De plus, GetStringAsyncet GetStreamAsyncgèrent en interne le délai d'expiration, ils ne seront JAMAIS lancés.

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
la source
2
J'ai testé ceci, et GetStreamAsyncjeté un TaskCanceledExceptionpour moi.
Sam
38
Comment puis-je savoir si cela TaskCanceledExceptionest causé par un délai d'attente HTTP et non par une annulation directe ou une autre raison?
UserControl
8
Vérification @UserControl TaskCanceledException.CancellationToken.IsCancellationRequested. Si faux, vous pouvez être raisonnablement certain qu'il s'agissait d'un délai d'attente.
Todd Menier
3
En fin de compte, vous ne pouvez pas compter sur IsCancellationRequestedle jeton d'exception lors de l'annulation directe comme je le pensais précédemment: stackoverflow.com/q/29319086/62600
Todd Menier
2
@testing Ils ne se comportent pas différemment. C'est juste que vous avez un jeton qui représentera la demande d'annulation de l'utilisateur et un interne (vous ne pouvez pas accéder et vous n'avez pas besoin) qui représente le délai d'expiration du client. C'est le cas d'utilisation qui diffère
Sir Rufo
59

Je reproduis le même problème et c'est vraiment ennuyeux. J'ai trouvé cela utile:

HttpClient - gestion des exceptions agrégées

Le bogue dans HttpClient.GetAsync doit lever WebException, pas TaskCanceledException

Du code au cas où les liens ne vont nulle part:

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
la source
D'après mon expérience, WebException ne peut être capturé en aucune circonstance. Les autres vivent-ils quelque chose de différent?
écraser
1
@crush WebException peut être attrapé. Cela aidera peut - être .
DavidRR
Cela ne fonctionne pas pour moi si je n'utilise pas cts. J'utilise juste Task <T> task = SomeTask () try {T result = task.Result} catch (TaskCanceledException) {} catch (Exception e) {} Seule l'exception générale est interceptée, pas l'exception TaskCanceledException. Quel est le problème dans ma version de code?
Naomi
1
J'ai créé un nouveau rapport de bogue puisque l'original semble être dans un message de forum archivé: connect.microsoft.com/VisualStudio/feedback/details/3141135
StriplingWarrior
1
Si le jeton est passé de l'extérieur, vérifiez ce n'est pas default(CancellationToken)avant de comparer avec ex.CancellationToken.
SerG
25

J'ai trouvé que le meilleur moyen de déterminer si l'appel de service a expiré est d'utiliser un jeton d'annulation et non la propriété timeout de HttpClient:

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

Et puis gérez l'exception CancellationException pendant l'appel de service ...

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

Bien sûr, si le délai d'expiration se produit du côté du service, cela devrait pouvoir être géré par une WebException.

Jack
la source
1
Hmm, je suppose que l'opérateur de négation (qui a été ajouté dans une modification) devrait être supprimé pour que cet échantillon ait un sens? Si cts.Token.IsCancellationRequestedest truecela doit signifier qu'un délai d' attente est survenue?
Lasse Christiansen
9

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

Une requête DNS (Domain Name System) peut prendre jusqu'à 15 secondes pour revenir ou expirer. Si votre demande contient un nom d'hôte qui nécessite une résolution et que vous définissez Timeout sur une valeur inférieure à 15 secondes, cela peut prendre 15 secondes ou plus avant qu'une WebException soit levée pour indiquer un délai d'expiration de votre demande .

Vous avez ensuite accès à la Statuspropriété, voir WebExceptionStatus

user247702
la source
3
Hm, je reviens AggregateExceptionavec un TaskCancelledExceptionintérieur. Je dois faire quelque chose de mal ...
Benjol
Utilisez-vous catch(WebException e)?
user247702
Non, et si j'essaye, le AggregateExceptionn'est pas géré. Si vous créez un projet de console VS, ajoutez une référence à System.Net.Httpet déposez le code dans main, vous pouvez voir par vous-même (si vous le souhaitez).
Benjol
5
Si la période d'attente dépasse le délai d'expiration de la tâche, vous obtiendrez un fichier TaskCanceledException. Cela semble être provoqué par la gestion du délai d'expiration interne du TPL, à un niveau supérieur au HttpWebClient. Il ne semble pas y avoir de bon moyen de faire la distinction entre une annulation de timeout et une annulation d'utilisateur. Le résultat de ceci est que vous ne pouvez pas obtenir un WebExceptiondans votre AggregateException.
JT.
1
Comme d'autres l'ont dit, vous devez supposer que l'exception TaskCanceledException était le délai d'expiration. J'utilise try {// Code here} catch (AggregateException exception) {if (exception.InnerExceptions.OfType <TaskCanceledException> () .Any ()) {// Handle timeout here}}
Vdex
8

Fondamentalement, vous devez attraper OperationCanceledExceptionet vérifier l'état du jeton d'annulation qui a été passé à SendAsync(ou GetAsync, ou quelle que soit la HttpClientméthode que vous utilisez):

  • si elle a été annulée ( IsCancellationRequestedest vrai), cela signifie que la demande a vraiment été annulée
  • sinon, cela signifie que la demande a expiré

Bien sûr, ce n'est pas très pratique ... il serait préférable de recevoir un TimeoutExceptionen cas de timeout. Je propose ici une solution basée sur un gestionnaire de message HTTP personnalisé: Meilleure gestion du délai d'expiration avec HttpClient

Thomas Levesque
la source
ah! c'est toi! J'ai écrit un commentaire dans votre article de blog plus tôt aujourd'hui. Mais à propos de cette réponse, je pense que votre point de vue sur IsCancellationRequested n'est pas vrai, car il semble que c'est toujours vrai pour moi, quand je ne l'ai pas annulé moi
knocte
@knocte c'est bizarre ... Mais dans ce cas, la solution de mon article de blog ne vous aidera pas, car elle repose également sur cela
Thomas Levesque
1
dans le problème github à ce sujet, beaucoup affirment ce que j'ai dit: que IsCancellationRequested est vrai lorsqu'il y a un délai d'expiration; je suis donc tenté de voter contre votre réponse;)
knocte
@knocte, je ne sais pas quoi vous dire ... J'utilise ça depuis longtemps et ça a toujours fonctionné pour moi. Avez-vous réglé le HttpClient.Timeoutsur l'infini?
Thomas Levesque
non je ne l'ai pas fait parce que je ne peux pas contrôler HttpClient moi-même, c'est une bibliothèque tierce que j'utilise, celle qui l'utilise
knocte
-1
_httpClient = new HttpClient(handler) {Timeout = TimeSpan.FromSeconds(5)};

est ce que je fais habituellement, semble fonctionner assez bien pour moi, c'est particulièrement bon lors de l'utilisation de proxies.

Développement Syv
la source
1
C'est ainsi que vous configurez le délai d'expiration de httpclient. Cela ne répond pas à la question, à savoir comment savoir quand httpclient a expiré.
Ethan Fischer le