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 使用它时,将其转换为任务或 运行 它。

在你的情况下你只是在做一件事。我想如果您有多个要以某种方式组合的异步步骤,则完全异步会更有用。

例如参见https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/asynchronous-and-concurrent-programming/async#combine-asynchronous-computations

正如另一个答案中提到的,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 以避免转换。