Reputation: 1406
I am trying to use HttpClient to send POST request in F#. Unfortunately I have found that timeouts in F# doesn't work. I have a code like this:
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 which you can see in code doesn't exist. I expect call to be cancelled after 2 seconds (cancellation token) but exception is never thrown ("Test 2" is never printed on screen). After PostAsync method call program control never returns into async block. Behavior is the same if I use HttpClient.Timeout property or Async.Catch instead of try block.
In C# the same code works as expected, after two seconds exception is raised:
private async Task Test()
{
var httpClient = new HttpClient();
await
httpClient.PostAsync("http://135.128.223.112", new StringContent("test"),
new CancellationTokenSource(2000).Token);
}
I know that async in F# and C# is different so I expect this be caused by some synchronization problem, deadlock ... I hope someone with deeper understanding of F# async can explain this and provide workaround.
UPDATE: I am starting F# async work like this:
Async.Start (asyncTest())
I am running this test in console appliation and I have ReadKey() at the end so app doesn't end sooner than async.
After rading Fyodor's comment I tried to change it to RunSynchronously. Now exception is raised but still I need it work with Async.Start.
Upvotes: 3
Views: 699
Reputation: 13577
What you are seeing is a consequence of two things:
OperationCancelledException
using a special path in F# async through the cancellation continuation. What it means in practice is that within the context of an async
block it's an "uncatchable" exception, bypassing try-with
, which works on the error continuation.AwaitTask
integrating the task you're waiting on to be part of your async
block - which includes promoting task cancellation into async cancellation (OperationCancelledException
instead of TaskCancelledException
) and bringing your entire workflow down.That's not an obvious behavior and it appears that it's being changed for the future versions of F#.
In the meantime, you can try something like this instead of 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)
Upvotes: 5