HttpClient 的超时在异步块内不起作用
Timeout for HttpClient doesn't work inside async block
我正在尝试使用 HttpClient 在 F# 中发送 POST 请求。不幸的是,我发现 F# 中的超时不起作用。我有这样的代码:
let asyncTest() = async {
let httpClient = new HttpClient()
try
let! res = httpClient.PostAsync("http://135.128.223.112", new StringContent("test"), (new CancellationTokenSource(2000)).Token) |> Async.AwaitTask
Console.WriteLine("Test 1")
with
| :? Exception as e -> Console.WriteLine("Test 2")
}
您在代码中看到的IP不存在。我希望调用在 2 秒后被取消(取消标记),但永远不会抛出异常("Test 2" 永远不会打印在屏幕上)。在 PostAsync 方法调用程序控制之后,永远不会 returns 进入异步块。如果我使用 HttpClient.Timeout 属性 或 Async.Catch 而不是 try 块,行为是相同的。
在 C# 中,相同的代码按预期工作,两秒后引发异常:
private async Task Test()
{
var httpClient = new HttpClient();
await
httpClient.PostAsync("http://135.128.223.112", new StringContent("test"),
new CancellationTokenSource(2000).Token);
}
我知道 F# 和 C# 中的异步是不同的,所以我认为这是由某些同步问题、死锁引起的...我希望对 F# 异步有更深入了解的人可以解释这一点并提供解决方法。
更新:
我正在像这样开始 F# 异步工作:
Async.Start (asyncTest())
我运行在控制台应用程序中进行此测试,最后我有 ReadKey(),因此应用程序不会比异步早结束。
阅读 Fyodor 的评论后,我尝试将其更改为 RunSynchronously。现在出现了异常,但我仍然需要它与 Async.Start.
一起使用
您所看到的是两件事的结果:
OperationCancelledException
通过取消延续在 F# 异步中使用特殊路径。它在实践中的意思是,在 async
块的上下文中,它是一个 "uncatchable" 异常,绕过 try-with
,它对错误继续起作用。
AwaitTask
将您正在等待的任务整合到您的 async
块中——包括将任务取消提升为异步取消(OperationCancelledException
而不是 TaskCancelledException
) 并降低整个工作流程。
这不是一个明显的行为,它似乎正在针对 F# 的未来版本进行更改。
与此同时,您可以尝试这样的操作而不是 Async.AwaitTask
:
let awaitTaskCancellable<'a> (task: Task<'a>) =
Async.FromContinuations(fun (cont, econt, ccont) ->
task.ContinueWith(fun (t:Task<'a>) ->
match t with
| _ when t.IsFaulted -> econt t.Exception
| _ when t.IsCanceled ->
// note how this uses error continuation
// instead of cancellation continuation
econt (new TaskCanceledException())
| _ -> cont t.Result) |> ignore)
我正在尝试使用 HttpClient 在 F# 中发送 POST 请求。不幸的是,我发现 F# 中的超时不起作用。我有这样的代码:
let asyncTest() = async {
let httpClient = new HttpClient()
try
let! res = httpClient.PostAsync("http://135.128.223.112", new StringContent("test"), (new CancellationTokenSource(2000)).Token) |> Async.AwaitTask
Console.WriteLine("Test 1")
with
| :? Exception as e -> Console.WriteLine("Test 2")
}
您在代码中看到的IP不存在。我希望调用在 2 秒后被取消(取消标记),但永远不会抛出异常("Test 2" 永远不会打印在屏幕上)。在 PostAsync 方法调用程序控制之后,永远不会 returns 进入异步块。如果我使用 HttpClient.Timeout 属性 或 Async.Catch 而不是 try 块,行为是相同的。
在 C# 中,相同的代码按预期工作,两秒后引发异常:
private async Task Test()
{
var httpClient = new HttpClient();
await
httpClient.PostAsync("http://135.128.223.112", new StringContent("test"),
new CancellationTokenSource(2000).Token);
}
我知道 F# 和 C# 中的异步是不同的,所以我认为这是由某些同步问题、死锁引起的...我希望对 F# 异步有更深入了解的人可以解释这一点并提供解决方法。
更新: 我正在像这样开始 F# 异步工作:
Async.Start (asyncTest())
我运行在控制台应用程序中进行此测试,最后我有 ReadKey(),因此应用程序不会比异步早结束。
阅读 Fyodor 的评论后,我尝试将其更改为 RunSynchronously。现在出现了异常,但我仍然需要它与 Async.Start.
一起使用您所看到的是两件事的结果:
OperationCancelledException
通过取消延续在 F# 异步中使用特殊路径。它在实践中的意思是,在async
块的上下文中,它是一个 "uncatchable" 异常,绕过try-with
,它对错误继续起作用。AwaitTask
将您正在等待的任务整合到您的async
块中——包括将任务取消提升为异步取消(OperationCancelledException
而不是TaskCancelledException
) 并降低整个工作流程。
这不是一个明显的行为,它似乎正在针对 F# 的未来版本进行更改。
与此同时,您可以尝试这样的操作而不是 Async.AwaitTask
:
let awaitTaskCancellable<'a> (task: Task<'a>) =
Async.FromContinuations(fun (cont, econt, ccont) ->
task.ContinueWith(fun (t:Task<'a>) ->
match t with
| _ when t.IsFaulted -> econt t.Exception
| _ when t.IsCanceled ->
// note how this uses error continuation
// instead of cancellation continuation
econt (new TaskCanceledException())
| _ -> cont t.Result) |> ignore)