问题
I have a set of F# scripts that call various libraries that we have created, many of them exposing asynchronous methods originally written in C#. Recently I found out the scripts stopped working (I think it's about half a year since I used them last time and they worked back then).
I was trying to isolate the problem and came up with the following code that reproduces it:
First, let's consider a library containing the following C# class:
public class AsyncClass
{
public async Task<string> GetStringAsync()
{
var uri = new Uri("https://www.google.com");
var client = new HttpClient();
var response = await client.GetAsync(uri);
var body = await response.Content.ReadAsStringAsync();
return body;
}
}
Next, let's call the library from F# (FSX script) using the following code:
let asyncClient = AsyncClass()
let strval1 = asyncClient.GetStringAsync() |> Async.AwaitTask |> Async.RunSynchronously
printfn "%s" strval1
let strval2 =
async {
return! asyncClient.GetStringAsync() |> Async.AwaitTask
} |> Async.RunSynchronously
printfn "%s" strval2
Obtaining strval1
ends up with a deadlock, whereas strval2
is retrieved just fine (I am quite sure the first scenario used to work too a couple of months ago so it looks like some sort of an update might have caused this).
This is most likely a synchronisation context issue where the thread is basically "waiting for itself to finish", but I don't understand what exactly is wrong with the first call - I can't see anything wrong with it.
Similar issues on StackOverflow:
- Why do I have to wrap an Async<T> into another async workflow and let! it? - this seems to be the same question, but not enough information is given and a simple reproducing example is missing
- Why is Async.RunSynchronously hanging? - this is similar but there is an obvious mistake the author has made
回答1:
So a .net Task
will start immediately, while F# async {}
is lazy. So when you wrap a task inside an async { }
it becomes lazy, and thus will have the characteristics that Async.RunSynchronously
is expecting.
Generally I use async {} when I'm doing f# asynchronous operations only, and if I'm working with .net Tasks I'll use TaskBuilder.fs (available in nuget). It's more aware of Task
idiosyncrasies like ConfigureAwait(continueOnCapturedContext: false)
.
open FSharp.Control.Tasks.V2.ContextInsensitive
task {
let! x = asyncClient.GetStringAsync()
//do something with x
}
来源:https://stackoverflow.com/questions/57869267/calling-c-sharp-async-method-from-f-results-in-a-deadlock