当HttpClient超时时,我该如何判断?

203
据我所知,没有办法确定它是特定的超时问题。我是否没有找对地方,或者我错过了更重要的东西?
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);
}

返回结果如下:

发生了一个或多个错误。

某个任务被取消。


3
我们可以在 GitHub 上为这个问题投赞成票:HttpClient throws TaskCanceledException on timeout #20296 - csrowell
这个问题值得大力点赞。 另外...有没有想法在UWP上如何实现?它的Windows.Web.HTTP.HTTPClient没有超时成员。而且GetAsync方法不接受取消令牌... - Do-do-new
1
六年过去了,似乎仍然无法知道客户端是否超时。 - Steve Smith
4
.NET 5终于实现了一个封装的TimeoutException,请查看下面我提供的示例实现。 - Knelis
6个回答

86

我正在复现同样的问题,这真的很烦人。我找到了以下有用信息:

HttpClient - 处理聚合异常

HttpClient.GetAsync中的错误应抛出WebException而非TaskCanceledException

如果链接失效,这里是一些代码:

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!?)
    }
}

根据我的经验,无论什么情况下都无法捕获WebException异常。其他人有不同的经历吗? - crush
1
@crush WebException 可以被捕获。也许这个会有所帮助。 - DavidRR
1
我创建了一个新的错误报告,因为原始报告似乎在存档的论坛帖子中:https://connect.microsoft.com/VisualStudio/feedback/details/3141135 - StriplingWarrior
1
如果从外部传递了令牌,请在与“ex.CancellationToken”进行比较之前检查它是否为“default(CancellationToken)”。 - SerG
如果我不能在这里使用await,但是httpclient.getasync与取消标记一起使用,我该怎么办?我可以像这样使用吗?_httpClient.GetAsync(url, cts.Token) .GetAwaiter() .GetResult(); - Armin Torkashvand
显示剩余2条评论

69

你需要等待GetAsync方法。如果超时,它会抛出一个TaskCanceledException。另外,GetStringAsyncGetStreamAsync在内部处理超时,因此它们永远不会抛出异常。

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

5
我测试了这个,GetStreamAsync 对我抛出了一个 TaskCanceledException 异常。 - Sam
49
жҲ‘еҰӮдҪ•зЎ®е®ҡTaskCanceledExceptionжҳҜз”ұHTTPи¶…ж—¶еј•иө·зҡ„пјҢиҖҢдёҚжҳҜзӣҙжҺҘеҸ–ж¶ҲжҲ–е…¶д»–еҺҹеӣ еј•иө·зҡ„пјҹ - UserControl
11
请检查 TaskCanceledException.CancellationToken.IsCancellationRequested 属性。如果为 false,你可以合理地确定这是由于超时引起的。 - Todd Menier
6
事实证明,你不能指望在直接取消操作时通过异常的标记来设置IsCancellationRequested,这与我之前的想法不同:https://dev59.com/810b5IYBdhLWcg3wUP4X - Todd Menier
3
他们的行为并没有不同。只是你有一个代表用户取消请求的标记,以及一个表示客户端超时的内部标记(你无法访问也不需要)。区别在于使用情况。 - Sir Rufo
显示剩余7条评论

67

从.NET 5开始,实现已更改HttpClient仍会抛出一个 TaskCanceledException,但现在将一个 TimeoutException 包装为 InnerException。因此,您可以轻松检查请求是被取消还是超时(代码示例摘自链接的博客文章):

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

这个方案很好,通过实现@Thomas Levesque提出的自定义HttpHandler来处理此问题时,可以省去所需的开销。只要您无法使用.NET 5或更新版本,这仍然是可行的。 - Robin Güldenpfennig
我使用System.Net.Http.Json中的*JsonAsync方法,当超时时它们会抛出System.Net.Http.HttpRequestException和内部的System.Net.Sockets.SocketException - sepulka
@RobinGüldenpfennig 但是为什么他们不以同样的方式修复 .Net Framework HTTPClient 呢? - 23W
@23W .NET Framework 4.8 正处于维护模式,因此微软只会提供与安全相关的修复,但不会在未来更改任何已建立的 API。如果您能够这样做,应该选择 .NET 6 和即将推出的版本。 - Robin Güldenpfennig

30

我发现确定服务调用是否已超时的最佳方法是使用取消令牌,而不是 HttpClient 的超时属性:

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

然后在服务调用期间处理CancellationException异常...

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

当然,如果超时是在服务端发生的,那么它应该能够被WebException处理。


3
嗯,我猜测否定运算符(在编辑中添加的)应该被移除,以使这个示例有意义?如果 cts.Token.IsCancellationRequested 的值为 true,那么就意味着发生了超时? - Lasse Christiansen

13

基本上,您需要捕获 OperationCanceledException 并检查传递给 SendAsync(或 GetAsync 或任何您正在使用的 HttpClient 方法)的取消标记的状态:

  • 如果它被取消了(IsCancellationRequested 为 true),这意味着请求确实被取消了
  • 如果没有,它意味着请求超时了

当然,这并不是很方便... 如果超时的情况下能够接收到一个 TimeoutException 就更好了。我在这里提出了一种基于自定义 HTTP 消息处理程序的解决方案:使用 HttpClient 更好的处理超时


啊!是你啊!我今天早些时候在你的博客文章中留了一条评论。但是关于这个答案,就“IsCancellationRequested”而言,我认为你的观点不正确,因为在我的情况下它似乎总是为真,即使我自己没有取消。 - knocte
1
@knocte 这很奇怪...但在这种情况下,我的博客文章中的解决方案将无法帮助您,因为它也依赖于此。 - Thomas Levesque
1
在关于这个问题的 Github 上,许多人声称我所说的是正确的:当超时时,IsCancellationRequested 为 true;因此我很想给你的答案点个踩 ;) - knocte
1
@knocte,我不知道该告诉你什么......我已经使用它很长时间了,而且它总是对我有效。你是否将 HttpClient.Timeout 设置为无限大? - Thomas Levesque
不,我没有这样做,因为我无法自己控制HttpClient,它是我正在使用的第三方库,是使用它的人。 - knocte
1
@knocte请查看Knelis的答案(2021年2月1日),.NET 5终于实现了一个包装的TimeoutException - Simple

10

3
嗯,我收到了一个包含“TaskCancelledException”的“AggregateException”。 我一定做错了什么... - Benjol
你是否正在使用 catch(WebException e) - user247702
不行,而且如果我尝试,AggregateException 也无法处理。如果你创建一个VS控制台项目,添加一个对 System.Net.Http 的引用,并将代码放入 main 中,你就可以自己看到(如果你想的话)。 - Benjol
6
如果等待时间超过任务的超时时间,你会收到一个“TaskCanceledException”异常。这似乎是由TPL内部超时处理引发的,比“HttpWebClient”更高级别。似乎没有一个好方法来区分超时取消和用户取消。结果是你可能不会在你的“AggregateException”中得到“WebException”。 - JT.
1
正如其他人所说,你必须假设TaskCanceledException是超时引起的。我正在使用以下代码: try{ //在此处编写代码 } catch (AggregateException exception) { if (exception.InnerExceptions.OfType<TaskCanceledException>().Any()) { //在此处处理超时 } } - Vdex
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接