c# - 带有 CancellationToken 的 HttpClient 在 Xamarin 应用程序中不起作用

标签 c# .net xamarin xamarin.forms httpclient

我有一个 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/

相关文章:

c# - 使用 CsvHelper 编写 CSV 文件时的自定义转换

ios - 通过Web View 登录

c# - 将 C++ 数组返回到 C#

c# - 在不使用任何类型的情况下加载许多托管 DLL 是否不好?

c# - 如何从 if 语句中的 bool 值中跳出 if 语句

c# - Parallel.Foreach 委托(delegate)调用另一个委托(delegate)

c# - Xamarin IOS 堆栈 View

c# - 用于跨平台 Xamarin Forms 的用户控件

c# - 如何使用 IEnumerable<T> 计算模型中的项目数

c# - 如何检查控件是否启用了触摸手势?