4

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

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

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

然后我想自己提供异步版本,方法是创建一个调用它并返回 (already started) 的 F# 函数Async<Bar>。也就是说,我不想使用System.Threading.Task<Bar>.

我想我正在寻找的是等同Task<T>.Run()于 F# Async 的做事方式。

我已经看过以下选项:

  • Async.StartAsTask-> 处理 C#ish 任务类型。
  • Async.StartAsync.StartImmediately->Async<unit>不接收Async<T>

Async.StartChild我要找的吗?如果是,那将是:

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

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

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

1 回答 1

6

BazWrapper 返回Async<Async<Bar>>是因为 StartChild 就是这样做的:它需要一个Async<'T>并返回Async<Async<'T>>。目的是在异步计算表达式中使用它,以便您可以启动多个“子”异步。Async.Start 与 Async.StartChild示例代码中的示例:

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

当您在async计算表达式中时,let!关键字将“展开”并为Async<'Whatever>您留下 type 的值'Whatever。在调用的情况下Async.StartChild'Whatever类型是具体的Async<'T>

因此,如果你想通过返回一个已经启动的异步Async.StartChild,方法是:

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

但是,我怀疑您会发现已经启动的异步对您而言不如“冷”异步(尚未启动的异步)有用,因为“冷”异步仍然可以与其他启动之前的异步任务。(例如,这对于将日志记录在您的异步中很有用。)因此,如果我正在为您的情况编写代码,我可能会简单地这样做:

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

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

于 2018-03-24T11:18:43.557 回答