Return 通过从 C# 调用非异步 Func<T> 在 F# 函数中启动 Async<T>?

Return started Async<T> in F# function from calling a non-async Func<T> from C#?

假设我想从 F# 调用这个 C# 函数:

public static class Foo {
    public Bar Baz()
    {
       ...
    }
}

问题是此功能 CPU 密集,我不想阻塞它。不幸的是,C# 库没有 Task<Bar> BazAsync() 重载。

然后我想自己提供异步版本,方法是创建调用它的 F# 函数和 returns(已经启动)Async<Bar>。也就是说,我不想使用 System.Threading.Task<Bar>.

我想我正在寻找的是 F# 异步处理方式中的 Task<T>.Run() 等价物。

我已经查看了以下选项:

Async.StartChild 是我要找的东西吗? 如果是,它将是:

let BazWrapper() =
    let asyncTask: Async<Bar> = async {
        return Foo.Bar()
    }
    Async.StartChild asyncTask

但是,如果以上是解决方案:

  1. 为什么大多数关于 F# 异步工作流的文档都没有提到 StartChild?
  2. 为什么 BazWrapper returns Async<Async<Bar>> 而不是 Async<Bar>

BazWrapper returns Async<Async<Bar>> 因为这就是 StartChild 所做的:它需要一个 Async<'T> 和 returns Async<Async<'T>>。目的是在异步计算表达式中使用它,以便您可以启动多个 "child" 异步。来自 Async.Start vs Async.StartChild 示例代码的示例:

async {
    //(...async stuff...)
    for msg in msgs do 
        let! child = asyncSendMsg msg |> Async.StartChild
        ()
    //(...more async stuff...)
}

当您在 async 计算表达式中时,let! 关键字将 "unwrap" 一个 Async<'Whatever> 为您留下类型 'Whatever 的值.在调用Async.StartChild的情况下,'Whatever类型具体为Async<'T>.

因此,如果您想通过 Async.StartChild return 一个 already-started 异步,方法是:

let BazWrapper() =
    let asyncTask: Async<Bar> = async {
        return Foo.Bar()
    }
    async {
        let! child = Async.StartChild asyncTask
        return! child
    }

但是,我怀疑您会发现 already-started 异步对您来说不如 "cold" 异步(尚未启动的异步)有用,因为 "cold" async 在启动之前仍然可以与其他异步任务组合。 (例如,这对于围绕您的异步进行日志记录很有用。)因此,如果我为您的情况编写代码,我可能会简单地这样做:

let BazWrapper() =
    async {
        return Foo.Bar()
    }

现在 BazWrapper() return 是一个尚未启动的 Async,如果您想要立即获得值,您可以使用 Async.RunSynchronously 启动它,或者在其他地方使用它async 计算表达式。