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.StartAsTask
-> 处理 C#ish 任务类型。
Async.Start
和 Async.StartImmediately
-> 接收 Async<unit>
而不是 Async<T>
Async.StartChild
是我要找的东西吗?
如果是,它将是:
let BazWrapper() =
let asyncTask: Async<Bar> = async {
return Foo.Bar()
}
Async.StartChild asyncTask
但是,如果以上是解决方案:
- 为什么大多数关于 F# 异步工作流的文档都没有提到 StartChild?
- 为什么 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
计算表达式。
假设我想从 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.StartAsTask
-> 处理 C#ish 任务类型。Async.Start
和Async.StartImmediately
-> 接收Async<unit>
而不是Async<T>
Async.StartChild
是我要找的东西吗?
如果是,它将是:
let BazWrapper() =
let asyncTask: Async<Bar> = async {
return Foo.Bar()
}
Async.StartChild asyncTask
但是,如果以上是解决方案:
- 为什么大多数关于 F# 异步工作流的文档都没有提到 StartChild?
- 为什么 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
计算表达式。