我有一个 Android Xamarin Forms 应用程序,我正在尝试使用 CancellationToken 为 HttpClient 设置超时,但它似乎不起作用。该请求似乎在大约 2 分钟后超时,而不是我预期的 5 秒:
private HttpClient _httpClient = new HttpClient(new HttpClientHandler() { UseProxy = false })
{
Timeout = TimeSpan.FromSeconds(5)
};
public async Task<T> GetAsync<T>(string url, TimeSpan timeout) where T : new()
{
using (var tokenSource = new CancellationTokenSource(timeout))
{
try
{
using (var response = await _httpClient.GetAsync(url, tokenSource.Token))
{
// never gets here when it times out on a bad address.
response.EnsureSuccessStatusCode();
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
if (response.IsSuccessStatusCode)
{
using (var textReader = new StreamReader(responseStream))
{
using (var jsonReader = new JsonTextReader(textReader))
{
return JsonSerializer.CreateDefault().Deserialize<T>(jsonReader);
}
}
}
else
{
return default(T);
}
}
}
}
catch (TaskCanceledException)
{
// this gets hit after about 2 minutes as opposed to the 5 seconds I expected.
return default(T);
}
catch
{
return default(T);
}
}
}
然后用法:
var myObject = await GetAsync<MyObject>("https://example.com/badRequest", TimeSpan.FromSeconds(5));
如果我从 .NET Core 应用程序运行相同的代码,它会按照我的预期工作(5 秒后超时)。
有人知道为什么 Mono 框架忽略取消 token 以及合理的解决方法是什么吗?
最佳答案
看来这和a well-known issue有关在旧版本的 .NET 上,Socket.ConnectAsync
(由 HttpClientHandler
在底层使用)不会调用 Dns.GetHostAddressesAsync
,它调用非异步版本,并忽略超时。这是now fixed在较新版本的 .NET 上。
作为解决方法,您可以先自行调用 Dns.GetHostAddressesAsync
。由于操作系统级缓存,这不会导致双重 DNS 查找。不幸的是,除非您使用的是较新版本的 .NET,否则 Dns.GetHostAddressesAsync
不接受 CancellationToken
。因此,您需要 Task.WhenAny
以及辅助函数。
public static Task WhenCanceled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
public async Task<T> GetAsync<T>(string url, TimeSpan timeout) where T : new()
{
using (var tokenSource = new CancellationTokenSource(timeout))
{
try
{
using (var dnsTask = Dns.GetHostAddressesAsync(new Uri(url).Host))
_ = await Task.WhenAny(WhenCanceled(tokenSource.Token), dnsTask);
tokenSource.Token.ThrowIfCancellationRequested();
using (var response = await _httpClient.GetAsync(url, tokenSource.Token))
{
response.EnsureSuccessStatusCode();
using (var responseStream = await response.Content.ReadAsStreamAsync(tokenSource.Token))
using (var textReader = new StreamReader(responseStream))
using (var jsonReader = new JsonTextReader(textReader))
{
return _serializer.Deserialize<T>(jsonReader);
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Timeout");
// this gets hit after about 2 minutes as opposed to the 5 seconds I expected.
return default(T);
}
catch
{
return default(T);
}
}
}
关于c# - 带有 CancellationToken 的 HttpClient 在 Xamarin 应用程序中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73133616/