Calling C# async method from F# results in a deadlock

六月ゝ 毕业季﹏ 提交于 2021-02-08 15:09:56

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!