通过从C#调用非异步Func <t>,返回F#函数中的异步<t>?

时间:2018-03-24 10:55:53

标签: asynchronous f# c#-to-f# computation-expression

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

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

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

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

我想我正在寻找的东西相当于F#异步处理方式中的Task<T>.Run()

我已经看过以下选项:

  • Async.StartAsTask - &gt;处理C#ish任务类型。
  • Async.StartAsync.StartImmediately - &gt;收到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>

1 个答案:

答案 0 :(得分:6)

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

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

当您在async计算表达式中时,let!关键字将“展开”Async<'Whatever>,并为您留下'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
    }

但是,我怀疑你会发现已经启动的异步对你来说不如“冷”异步(一个尚未启动的异步)有用,因为“冷”异步仍然可以在启动之前由其他异步任务组成。 (这可以用于,例如,包装你的asyncs周围的日志。)所以,如果我正在为你的情况编写代码,我可能只是这样做:

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

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