从F#调用C#异步方法会导致死锁 [英] Calling C# async method from F# results in a deadlock
问题描述
我有一组F#脚本,它们调用了我们创建的各种库,其中许多暴露了最初用C#编写的异步方法.最近,我发现这些脚本停止工作了(我认为自从我上次使用它们已经过去了大约半年,然后又恢复了工作.)
我试图找出问题所在,并提出了以下代码来重现该问题:
首先,让我们考虑一个包含以下C#类的库:
公共类AsyncClass{公共异步Task< string>GetStringAsync(){var uri = new Uri("https://www.google.com");var client = new HttpClient();var response =等待client.GetAsync(uri);var body =等待响应.Content.ReadAsStringAsync();返回身体}}
接下来,让我们使用以下代码从F#(FSX脚本)中调用该库:
让asyncClient = AsyncClass()让strval1 = asyncClient.GetStringAsync()|>Async.AwaitTask |>异步运行printfn%s" strval1令strval2 =异步{返回!asyncClient.GetStringAsync()|>异步等待任务} |>异步运行printfn%s" strval2
获得 strval1
最终会导致死锁,而 strval2
的检索就很好了(我很确定第一种情况在几个月前就可以工作了,所以看起来可能是某种更新引起的).
这很可能是同步上下文问题,其中线程基本上是等待自身完成",但是我不明白第一次调用到底有什么问题-我看不到有什么问题./p>
StackOverflow上的类似问题:
- 为什么我必须包装Async< T>进入另一个异步工作流程,然后开始吧!-这似乎是同一个问题,但是给出的信息不足,并且缺少一个简单的复制示例
- 为什么Async.RunSynchronously挂起?-类似,但是有一个作者犯的明显错误
因此,.net Task
将立即启动,而F# async {}
则是懒惰的.因此,当您将任务包装在 async {}
内时,它变得很懒,因此具有 Async.RunSynchronously
所期望的特性.
通常,我仅在执行f#异步操作时才使用async {},如果我正在使用.net任务,则将使用 Next, let's call the library from F# (FSX script) using the following code: Obtaining 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: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
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).
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
}
这篇关于从F#调用C#异步方法会导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!