208

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.

2

6 Answers 6

87

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!?)
    }
}
7
  • In my experience, WebException can't be caught in any circumstance. Are others experiencing something different?
    – crush
    Mar 24, 2015 at 16:45
  • 1
    @crush WebException can be caught. Perhaps this will help.
    – DavidRR
    May 25, 2016 at 13:19
  • This doesn't work for me if I am not using cts. I'm just using Task<T> 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, 2016 at 14:06
  • 1
    I created a new bug report since the original one appears to be in an archived forum post: connect.microsoft.com/VisualStudio/feedback/details/3141135 Sep 15, 2017 at 21:30
  • 1
    If token is passed from outside check it's not default(CancellationToken) before comparing with ex.CancellationToken.
    – SerG
    Jun 18, 2018 at 11:51
74

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);   
}
4
  • 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. Oct 27, 2021 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 2, 2022 at 12:19
  • @RobinGüldenpfennig But why they don't fix .Net Framework HTTPClient in the same way?
    – 23W
    Sep 6, 2022 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. Sep 12, 2022 at 11:57
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);
}
12
  • 5
    I tested this, and GetStreamAsync threw a TaskCanceledException for me.
    – Sam
    Feb 18, 2013 at 5:12
  • 49
    How can I tell if TaskCanceledException is caused by HTTP timeout and not, say direct cancellation or other reason? Mar 26, 2014 at 5:51
  • 11
    @UserControl check TaskCanceledException.CancellationToken.IsCancellationRequested. If false, you can be reasonably certain it was a timeout. Apr 28, 2014 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: stackoverflow.com/q/29319086/62600 Mar 30, 2015 at 18:04
  • 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, 2017 at 11:00
32

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.

1
  • 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? Oct 18, 2019 at 12:38
14

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

6
  • 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, 2019 at 6: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 Oct 20, 2019 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, 2019 at 2: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? Oct 21, 2019 at 8:50
  • 1
    @knocte Check Knelis answer (Feb 1 2021), .NET 5 finally implements a wrapped TimeoutException.
    – Simple
    May 16, 2021 at 3: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

10
  • 3
    Hm, I'm getting back an AggregateException with a TaskCancelledException inside. I must be doing something wrong...
    – Benjol
    May 11, 2012 at 8:34
  • Are you using catch(WebException e)?
    – user247702
    May 11, 2012 at 8: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, 2012 at 8:40
  • 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 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, 2012 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<TaskCanceledException>().Any()) { //Handle timeout here } }
    – Vdex
    Jan 2, 2013 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.