从 F# 调用 C# 异步方法会导致死锁 [英] Calling C# async method from F# results in a deadlock
问题描述
我有一组 F# 脚本,它们调用我们创建的各种库,其中许多公开了最初用 C# 编写的异步方法.最近我发现脚本停止工作了(我想距离我上次使用它们大约有半年时间,然后他们工作了).
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:
首先,让我们考虑一个包含以下 C# 类的库:
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;
}
}
接下来,让我们使用以下代码从 F#(FSX 脚本)调用库:
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
获得 strval1
以死锁告终,而 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.
StackOverflow 上的类似问题:
Similar issues on StackOverflow:
- 为什么我必须包装一个 Async
进入另一个异步工作流程,让! - 这似乎是同一个问题,但没有提供足够的信息,并且缺少一个简单的复制示例 - 为什么 Async.RunSynchronously 挂起? - 这很相似,但有一个作者犯了一个明显的错误
- 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
推荐答案
所以 .net Task
将立即启动,而 F# async {}
是惰性的.因此,当您将任务包装在 async { }
中时,它会变得懒惰,因此将具有 Async.RunSynchronously
所期望的特征.
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.
通常我只在执行 f# 异步操作时使用 async {},如果我使用 .net 任务,我将使用 TaskBuilder.fs(在 nuget 中可用).它更了解 Task
特性,例如 ConfigureAwait(continueOnCapturedContext: false)
.
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屋!