201

As far as I can tell, there's no way to know that it's specifically a timeout that has occurred. Am I not looking in the right place, or am I missing something bigger?

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

This returns:

One or more errors occurred.

A task was canceled.

Community
  • 1
  • 1
Benjol
  • 63,995
  • 54
  • 186
  • 268
  • 3
    We can upvote the issue on GitHub: [HttpClient throws TaskCanceledException on timeout #20296](https://github.com/dotnet/corefx/issues/20296#issuecomment-369638372) – csrowell Mar 01 '18 at 17:11
  • Huge upvote for the question. Also... any idea how to do this on UWP? Its Windows.Web.HTTP.HTTPClient does not have timeout member. Also GetAsync method does not accept cancellation token... – Do-do-new Aug 16 '18 at 07:48

6 Answers6

86

I am reproducing the same issue and it's really annoying. I've found these useful:

HttpClient - dealing with aggregate exceptions

Bug in HttpClient.GetAsync should throw WebException, not TaskCanceledException

Some code in case the links go nowhere:

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!?)
    }
}
Community
  • 1
  • 1
vezenkov
  • 4,009
  • 1
  • 26
  • 27
  • In my experience, WebException can't be caught in any circumstance. Are others experiencing something different? – crush Mar 24 '15 at 16:45
  • 1
    @crush `WebException` *can* be caught. Perhaps [this](http://stackoverflow.com/q/9455657/1497596) will help. – DavidRR May 25 '16 at 13:19
  • This doesn't work for me if I am not using cts. I'm just using Task task = SomeTask() try { T result = task.Result} catch (TaskCanceledException) {} catch (Exception e) {} Only the general exception is caught, not the TaskCanceledException. What is wrong in my version of code? – Naomi Nov 22 '16 at 14:06
  • @Naomi, please share the type of the general exception being caught. Something like e.GetType().ToString() should give you the type string. Check the inner exception as well. A general exception catch could be hit by anything... – vezenkov Jan 25 '17 at 14:44
  • 1
    I created a new bug report since the original one appears to be in an archived forum post: https://connect.microsoft.com/VisualStudio/feedback/details/3141135 – StriplingWarrior Sep 15 '17 at 21:30
  • 1
    If token is passed from outside check it's not `default(CancellationToken)` before comparing with `ex.CancellationToken`. – SerG Jun 18 '18 at 11:51
  • What if I can't use await here, but the **httpclient.getasync** is used with cancellation token?. Can I use it like this? _httpClient.GetAsync(url, cts.Token) .GetAwaiter() .GetResult(); – Armin Torkashvand Jul 12 '22 at 15:47
69

You need to await the GetAsync method. It will then throw a TaskCanceledException if it has timed out. Additionally, GetStringAsync and GetStreamAsync internally handle timeout, so they will NEVER throw.

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
  • 723
  • 5
  • 4
  • 5
    I tested this, and `GetStreamAsync` threw a `TaskCanceledException` for me. – Sam Feb 18 '13 at 05:12
  • 49
    How can I tell if `TaskCanceledException` is caused by HTTP timeout and not, say direct cancellation or other reason? – UserControl Mar 26 '14 at 05:51
  • 11
    @UserControl check `TaskCanceledException.CancellationToken.IsCancellationRequested`. If false, you can be reasonably certain it was a timeout. – Todd Menier Apr 28 '14 at 22:29
  • 6
    As it turns out, you can't count on `IsCancellationRequested` getting set on the exception's token on direct cancellation as I previously thought: http://stackoverflow.com/q/29319086/62600 – Todd Menier Mar 30 '15 at 18:04
  • @ToddMenier: So how do you differentiate between user cancellation and timeout then? – testing May 31 '17 at 17:21
  • @testing To have user cancellation you must pass a CancellationToken to GetAsync. If that task was cancelled check that passed CancellationToken for a cancellation request and you will know – Sir Rufo Jun 25 '17 at 11:12
  • @SirRufo: Thats the thing which is not working. See the comment from Todd. – testing Jun 26 '17 at 09:44
  • 1
    @testing Todd wants to use the CancellationToken property from the exception and that does not work. I recommend to use the token you pass to GetAsync and that will work – Sir Rufo Jun 26 '17 at 10:42
  • @SirRufo: Seems that you are correct. Why do these two tokens behave differently? – testing Jun 26 '17 at 10:53
  • 3
    @testing They don't *behave* different. It is just that you have one token that will represent the user cancellation request and an internal (you cannot access and you don't need) that represents the client timeout. It is the use case that differs – Sir Rufo Jun 26 '17 at 11:00
  • @testing See the source of HttpClient where Timeout and passed Token are handled https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L417 – Sir Rufo Jun 26 '17 at 11:17
  • See this answer: https://stackoverflow.com/a/29319312/2394945 which links to this bug: https://connect.microsoft.com/VisualStudio/feedback/details/1214051 which states that it will be fixed in .NET 4.7.2 – Moby Disk Oct 27 '17 at 15:34
63

As of .NET 5, the implementation has changed. HttpClient still throws a TaskCanceledException, but now wraps a TimeoutException as InnerException. So you can easily check whether a request was canceled or timed out (code sample copied from linked blog post):

try
{
    using var response = await _client.GetAsync("http://localhost:5001/sleepFor?seconds=100");
}
// Filter by InnerException.
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // Handle timeout.
    Console.WriteLine("Timed out: "+ ex.Message);
}
catch (TaskCanceledException ex)
{
    // Handle cancellation.
    Console.WriteLine("Canceled: " + ex.Message);   
}
Knelis
  • 6,782
  • 2
  • 34
  • 54
  • This works fine and puts aside the needed overhead when handling this issue by implementing a custom HttpHandler as proposed by @Thomas Levesque. Which is still fine as long as you are not able to use .NET 5 or newer. – Robin Güldenpfennig Oct 27 '21 at 13:51
  • I use *JsonAsync methods from `System.Net.Http.Json` and they throw `System.Net.Http.HttpRequestException` with inner `System.Net.Sockets.SocketException` on timeout. – sepulka Feb 02 '22 at 12:19
  • @RobinGüldenpfennig But why they don't fix .Net Framework HTTPClient in the same way? – 23W Sep 06 '22 at 11:37
  • @23W .NET Framework 4.8 is on maintenance mode so Microsoft will only provide security related fixes but won't change any established APIs in the future. If you are able to do so you should go with .NET 6 and upcoming releases. – Robin Güldenpfennig Sep 12 '22 at 11:57
30

I found that the best way to determine if the service call has timed out is to use a cancellation token and not the HttpClient's timeout property:

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

And then handle the CancellationException during the service call...

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

Of course if the timeout occurs on the service side of things, that should be able to handled by a WebException.

piedar
  • 2,599
  • 1
  • 25
  • 37
Jack
  • 2,503
  • 1
  • 21
  • 15
  • 3
    Hmm, I guess that the negation operator (which was added in an edit) should be removed for this sample to make sense? If `cts.Token.IsCancellationRequested` is `true` it must mean that a timeout has occured? – Lasse Christiansen Oct 18 '19 at 12:38
12

Basically, you need to catch the OperationCanceledException and check the state of the cancellation token that was passed to SendAsync (or GetAsync, or whatever HttpClient method you're using):

  • if it was canceled (IsCancellationRequested is true), it means the request really was canceled
  • if not, it means the request timed out

Of course, this isn't very convenient... it would be better to receive a TimeoutException in case of timeout. I propose a solution here based on a custom HTTP message handler: Better timeout handling with HttpClient

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • ah! it's you! I wrote a comment in your blogpost earlier today. But wrt to this answer, I think your point about IsCancellationRequested is not true, because it seems it's always true for me, when I didn't cancel it myself – knocte Oct 19 '19 at 06:26
  • 1
    @knocte that's weird... But in that case, the solution from my blog post won't help you, since it also relies on this – Thomas Levesque Oct 20 '19 at 14:17
  • 1
    in the github issue about this, many claim what I said: that IsCancellationRequested is true when there is a timeout; so I'm tempted to downvote your answer ;) – knocte Oct 21 '19 at 02:35
  • 1
    @knocte, I don't know what to tell you... I've been using this for a long time and it always worked for me. Did you set the `HttpClient.Timeout` to infinity? – Thomas Levesque Oct 21 '19 at 08:50
  • no I didn't because I can't control HttpClient myself, it's a thirdparty library that I'm using, the one that uses it – knocte Oct 21 '19 at 11:42
  • 1
    @knocte Check Knelis answer (Feb 1 2021), .NET 5 finally implements a wrapped `TimeoutException`. – Simple May 16 '21 at 03:01
10

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

A Domain Name System (DNS) query may take up to 15 seconds to return or time out. If your request contains a host name that requires resolution and you set Timeout to a value less than 15 seconds, it may take 15 seconds or more before a WebException is thrown to indicate a timeout on your request.

You then get access to the Status property, see WebExceptionStatus

user247702
  • 23,641
  • 15
  • 110
  • 157
  • 3
    Hm, I'm getting back an `AggregateException` with a `TaskCancelledException` inside. I must be doing something wrong... – Benjol May 11 '12 at 08:34
  • Are you using `catch(WebException e)`? – user247702 May 11 '12 at 08:37
  • Nope, and if I try, the `AggregateException` is unhandled. If you create a VS console project, add a reference to `System.Net.Http` and drop the code into `main`, you can see for yourself (if you want to). – Benjol May 11 '12 at 08:40
  • I won't have access to a VS11 machine for another 5 hours or so, sorry. – user247702 May 11 '12 at 08:43
  • What if you drop the `Result` and use just `var s = client.GetAsync("")`, can you try that? Take a look at the [example](http://msdn.microsoft.com/en-us/library/system.net.http.httpclient.aspx). – user247702 May 11 '12 at 08:45
  • Nothing happens, because nobody is there to listen to the Task when it returns. Weirdly, if I do `client.GetAsync("").ContinueWith(t => Console.WriteLine(t.IsFaulted))` it says `False`... – Benjol May 11 '12 at 09:05
  • I'll look into it when I get home, if you haven't found it yet then. – user247702 May 11 '12 at 09:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11145/discussion-between-benjol-and-stijn) – Benjol May 11 '12 at 09:11
  • 6
    If the wait period exceeds the task's timeout period, you'll get a `TaskCanceledException`. This seems to be thrown by the TPL's internal timeout handling, at a higher level than the `HttpWebClient`. There [doesn't seem to be a good way](http://stackoverflow.com/questions/12666922/httpclient-distinguish-timeout-from-user-cancellation) to distinguish between a timeout cancellation and a user cancellation. The upshot of this is that you may not get a `WebException` within your `AggregateException`. – JT. Nov 18 '12 at 22:21
  • 1
    As Others have said, you have to assume the TaskCanceledException was the timeout. I am using try{ //Code here } catch (AggregateException exception) { if (exception.InnerExceptions.OfType().Any()) { //Handle timeout here } } – Vdex Jan 02 '13 at 13:31