Grokking F# 异步与 System.Threading.Task
Grokking F# async vs. System.Threading.Task
让我们考虑一下我的函数 loadCustomerProjection
:
let loadCustomerProjection id =
use session = store.OpenSession()
let result =
session.Load<CustomerReadModel>(id)
match result with
| NotNull -> Some result
| _ -> None
session.Load
是同步的并且returns一个CustomerReadModel
session
还提供了一个 LoadAsync
方法,我一直在这样使用它:
let loadCustomerProjection id =
use session = store.OpenSession()
let y = async {
let! x = session.LoadAsync<CustomerReadModel>(id) |> Async.AwaitTask
return x
}
let x = y |> Async.RunSynchronously
match x with
| NotNull -> Some x
| _ -> None
我想了解的是:
第二个版本是否有意义,它是否在非阻塞行为方面增加了任何价值,因为两者具有相同的签名:Guid -> CustomerReadModel option
?
如果使用 Giraffe HttpHandler 调用 loadCustomerProjection
,使用此签名 Guid -> Async<CustomerReadModel>
是否更有意义?
或者考虑到 Giraffe 上下文,签名会更好 Guid -> Task<CustomerReadModel>
?
在我的 Giraffe 处理程序中,我至少想做的是通过模式匹配将 null
结果处理为 404 - 所以在某些时候我需要进行 |> Async.AwaitTask
调用。
因为 Task 和 Async 之间的主要区别在于,当您有一个 Task 时,它总是热的,而 Async 是冷的(直到您决定 运行 它),因此使用 Async 是有意义的在您的后端代码中,然后当 Giraffe 使用它时,将其转换为任务或 运行 它。
在你的情况下你只是在做一件事。我想如果您有多个要以某种方式组合的异步步骤,则完全异步会更有用。
正如另一个答案中提到的,Task 和 Async 之间的主要区别在于 Task 是立即启动的,而 Async 必须显式启动。
在上面的简单示例中,返回 'T、Async<'T> 或 Task<'T> 不会有太大区别。我的偏好可能是 Task<'T>,因为这是您在函数中收到的内容,继续传播 Task<_> 直到最终使用点是有意义的。
请注意,当您使用 Giraffe 时,您应该可以访问 TaskBuilder.fs,它在 FSharp.Control.Tasks.V2
模块中为您提供了一个 task { }
计算表达式。
在这种特殊情况下,您的第一个版本和第二个版本之间没有区别,只是您在第二个版本中经历了所有的环节来调用异步函数,然后立即等待它。但是第二个版本和第一个版本完全一样。
至于其他答案中的热评和冷评;因为您将对 LoadAsync 的调用包装在异步计算表达式中,所以它保持冷(因为异步表达式仅在 运行 时执行)。另一方面,如果你写
let y =
session.LoadAsync<CustomerReadModel>(id)
|> Async.AwaitTask
然后 LoadAsync 将立即开始执行。
如果你想支持异步操作,那么让整个函数异步确实有意义:
let loadCustomerProjection id =
async {
use session = store.OpenSession()
let! x = session.LoadAsync<CustomerReadModel>(id) |> Async.AwaitTask
match x with
| NotNull -> return Some x
| _ -> return None
}
使用 Task 还是 Async 由您决定。我个人更喜欢 Async,因为它是 F# 原生的。但在您的情况下,您可能希望坚持使用 Giraffe's decision to stick with Task 以避免转换。
让我们考虑一下我的函数 loadCustomerProjection
:
let loadCustomerProjection id =
use session = store.OpenSession()
let result =
session.Load<CustomerReadModel>(id)
match result with
| NotNull -> Some result
| _ -> None
session.Load
是同步的并且returns一个CustomerReadModel
session
还提供了一个 LoadAsync
方法,我一直在这样使用它:
let loadCustomerProjection id =
use session = store.OpenSession()
let y = async {
let! x = session.LoadAsync<CustomerReadModel>(id) |> Async.AwaitTask
return x
}
let x = y |> Async.RunSynchronously
match x with
| NotNull -> Some x
| _ -> None
我想了解的是:
第二个版本是否有意义,它是否在非阻塞行为方面增加了任何价值,因为两者具有相同的签名:Guid -> CustomerReadModel option
?
如果使用 Giraffe HttpHandler 调用 loadCustomerProjection
,使用此签名 Guid -> Async<CustomerReadModel>
是否更有意义?
或者考虑到 Giraffe 上下文,签名会更好 Guid -> Task<CustomerReadModel>
?
在我的 Giraffe 处理程序中,我至少想做的是通过模式匹配将 null
结果处理为 404 - 所以在某些时候我需要进行 |> Async.AwaitTask
调用。
因为 Task 和 Async 之间的主要区别在于,当您有一个 Task 时,它总是热的,而 Async 是冷的(直到您决定 运行 它),因此使用 Async 是有意义的在您的后端代码中,然后当 Giraffe 使用它时,将其转换为任务或 运行 它。
在你的情况下你只是在做一件事。我想如果您有多个要以某种方式组合的异步步骤,则完全异步会更有用。
正如另一个答案中提到的,Task 和 Async 之间的主要区别在于 Task 是立即启动的,而 Async 必须显式启动。
在上面的简单示例中,返回 'T、Async<'T> 或 Task<'T> 不会有太大区别。我的偏好可能是 Task<'T>,因为这是您在函数中收到的内容,继续传播 Task<_> 直到最终使用点是有意义的。
请注意,当您使用 Giraffe 时,您应该可以访问 TaskBuilder.fs,它在 FSharp.Control.Tasks.V2
模块中为您提供了一个 task { }
计算表达式。
在这种特殊情况下,您的第一个版本和第二个版本之间没有区别,只是您在第二个版本中经历了所有的环节来调用异步函数,然后立即等待它。但是第二个版本和第一个版本完全一样。
至于其他答案中的热评和冷评;因为您将对 LoadAsync 的调用包装在异步计算表达式中,所以它保持冷(因为异步表达式仅在 运行 时执行)。另一方面,如果你写
let y =
session.LoadAsync<CustomerReadModel>(id)
|> Async.AwaitTask
然后 LoadAsync 将立即开始执行。
如果你想支持异步操作,那么让整个函数异步确实有意义:
let loadCustomerProjection id =
async {
use session = store.OpenSession()
let! x = session.LoadAsync<CustomerReadModel>(id) |> Async.AwaitTask
match x with
| NotNull -> return Some x
| _ -> return None
}
使用 Task 还是 Async 由您决定。我个人更喜欢 Async,因为它是 F# 原生的。但在您的情况下,您可能希望坚持使用 Giraffe's decision to stick with Task 以避免转换。