如何在 F# 异步表达式中使用 while 循环?

Ada*_*dam 3 f# c#-to-f#

我该如何用 F# 编写以下内容?

var reader = await someDatabaseCommand.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
  var foo = reader.GetDouble(1);
  // ... 
}
Run Code Online (Sandbox Code Playgroud)

我对 F# 很陌生,所以这是我能想到的最好的方法

task { 
  let! reader = someDatabaseCommand.ExecuteReaderAsync ()
  let! tmp1 = reader.ReadAsync ()
  let mutable hasNext = tmp1
  while hasNext do
    // ...
    let! tmp2 = reader.ReadAsync ()
    hasNext <- tmp2
}
Run Code Online (Sandbox Code Playgroud)

我知道它是一个 C# 库,所以它不会是完美的,但我想避免需要将结果存储ReadAsync在变量中,更不用说有两个临时变量了。我不知道有什么方法可以在Task不将其绑定到名称的情况下获得 a 的“结果”。也许递归解决方案会起作用?

Tom*_*cek 6

await该库与 C# 配合得非常好,因为您可以在循环条件中使用。我认为在 F# 中使用递归计算表达式比命令式解决方案好一点(我认为您无法简化它),但长度大致相同:

task {
  let! reader = someDatabaseCommand.ExecuteReaderAsync ()
  let rec loop () = task { 
    let! hasNext = reader.ReadAsync ()
    if hasNext then
      // ..
      return! loop () }
  return! loop ()
}
Run Code Online (Sandbox Code Playgroud)

更复杂的解决方案是使用 F# 异步序列(来自FSharp.Control.AsyncSeq 库)。我认为这只适用于async(所以你必须使用它而不是task),但它使代码更好。您可以定义一个运行命令并返回异步结果序列的函数(为简单起见,我只返回reader,这有点脏,但有效):

#r "nuget: FSharp.Control.AsyncSeq"
#r "nuget: System.Data.SqlClient"
open FSharp.Control

let runCommand (cmd:System.Data.SqlClient.SqlCommand) = asyncSeq { 
  let! reader = cmd.ExecuteReaderAsync () |> Async.AwaitTask
  let rec loop () = asyncSeq { 
    let! hasNext = reader.ReadAsync () |> Async.AwaitTask
    if hasNext then
      yield reader
      yield! loop () }
  yield! loop () }
Run Code Online (Sandbox Code Playgroud)

for鉴于此,您可以使用内部普通循环很好地迭代结果async(使用 AsyncSeq 库添加的重载):

async { 
  for reader in runCommand someDatabaseCommand do
    let foo = reader.GetDouble(1) 
    () }
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,任务不是尾递归的,因此它可能会在较长的序列上快速崩溃 (2认同)