C# - 使用HttpClientHandler取消不起作用的HttpClient

8
我在使用HttpClient (.NETStandard v2.1, System.Net.Http, targeting mono runtime)时遇到了一个问题。我想通过在SendAsync中传递CancellationToken来为每个请求设置HttpClient的超时时间。
当使用HttpClient的无参数构造函数时,它可以正常工作,但是当将HttpClientHandler实例传递给HttpClient构造函数时,它被忽略了。操作在75秒后被取消。
举个例子:
public static async Task<HttpResponseMessage> Send()
{
    var req = new HttpRequestMessage(HttpMethod.Get, new Uri("someURL"));
    var handler = new HttpClientHandler{ CookieContainer = new CookieContainer() };  
    /* var client = new HttpClient(); <--- This is working */          
    var client = new HttpClient(handler);
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    cts.Token.Register(() => Debug.WriteLine("TASK CANCELLED"));
    return await client.SendAsync(req, cts.Token);
}

在5秒后,调试输出会写入“任务取消”,但SendAsync仍然持续了75秒。如果使用无参构造函数,则SendAsync将在5秒后取消。
我需要HttpClientHandler使用CookieContainer属性。 我错过了什么?

编辑

经过进一步调查和 @Lasse Vågsæther Karlsen 的提示,我发现以下内容:

  1. .NET Core 3 运行时

    • 如果 URL 不可达或者网络连接断开,HttpClient 会在 3 秒后抛出 HttpRequestException
    • 否则它按预期在 5 秒后取消
  2. Mono 运行时(Xamarin.Forms 项目)

    • 如果 URL 不可达或者网络连接断开,HttpClient 会在 75 秒后抛出 OperationCanceledException
    • 否则它按预期在 5 秒后取消

可能与 this 有关。

尽管如此,仍存在一个未解决的问题...


我在 LINQPad 6 中使用 .NET Core 3 进行了一个简单的测试,使用了你的代码,在 5 秒后被取消了。你是在哪个运行时中运行这个代码的? - Lasse V. Karlsen
@LasseVågsætherKarlsen 这段代码位于一个.NETStandard v2.1类中,该类被引用于一个Xamarin.Forms项目中。 - Γαβριήλ Σαμώλης
.NET Standard不是运行时,你可以在.NET Framework和.NET Core上运行你的代码,但Xamarin很有帮助。这意味着你正在运行mono运行时。 - Lasse V. Karlsen
@LasseVågsætherKarlsen 你对这个行为有什么看法?如果在.NET Core中正常工作,是不是因为Mono运行时的原因呢? - Γαβριήλ Σαμώλης
@ΓαβριήλΣαμώλης 你可以在回答中分享你的解决方案并标记它。谢谢~ - Wendy Zang - MSFT
@WendyZang 我认为 HttpClient 和 mono 运行时可能存在问题。作为一个中等的解决方法,我使用了这个插件 链接,它提供了一个名为 IsRemoteReachable 的属性,在 Android 平台实现中,它会创建一个新的 Socket 连接到指定的 url,并使用 timeout,我在进行 http 请求 之前使用它来指定可达性,超时时间为 1000ms。我不认为这是一个解决方案,仍在寻找更优雅的解决方法。 - Γαβριήλ Σαμώλης
1个回答

1

我找到的最简单的方法是使用Polly库,并包装那些不支持CancellationToken的方法。甚至不需要CancellationToken。例如:

var policy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(3000),
    TimeoutStrategy.Pessimistic,
    (context, timespan, task) => throw new Exception("Cannot connect to server."));

await policy.ExecuteAsync(async () =>
{
    var httpClient = new HttpClient();
    var response = await httpClient.PostAsync(...);
    response.EnsureSuccessStatusCode();
    ...
});

是的,你确实需要 CancellationToken。你的设置不会在3秒后取消请求。为了使用乐观超时,你需要像这样使用 ExecuteAsync.ExecuteAsync(async ct => { ... httpClient.PostAsync(..., ct); ... }, CancellationToken.None); - Peter Csala

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