在异步计算表达式中,当条件为异步时使用'while'

12

我正在尝试在F#中使用SqlClient,使用SqlDataReader.ReadAsync时遇到了困难。我正在尝试实现以下F#等效代码:

while (await reader.ReadAsync) { ... }

请问在F#中最佳的做法是什么?以下是我的完整程序。它可以工作,但我想知道是否有更好的方法。

open System
open System.Data.SqlClient
open System.Threading.Tasks

let connectionString = "Server=.;Integrated Security=SSPI"

module Async =
    let AwaitVoidTask : (Task -> Async<unit>) =
        Async.AwaitIAsyncResult >> Async.Ignore

    // QUESTION: Is this idiomatic F#? Is there a more generally-used way of doing this?
    let rec While (predicateFn : unit -> Async<bool>) (action : unit -> unit) : Async<unit> = 
        async {
            let! b = predicateFn()
            match b with
                | true -> action(); do! While predicateFn action
                | false -> ()
        }

[<EntryPoint>]
let main argv = 
    let work = async {
        // Open connection
        use conn = new SqlConnection(connectionString)
        do! conn.OpenAsync() |> Async.AwaitVoidTask

        // Execute command
        use cmd = conn.CreateCommand()
        cmd.CommandText <- "select name from sys.databases"
        let! reader = cmd.ExecuteReaderAsync() |> Async.AwaitTask

        // Consume reader

        // I want a convenient 'while' loop like this...
        //while reader.ReadAsync() |> Async.AwaitTask do // Error: This expression was expected to have type bool but here has type Async<bool>
        //    reader.GetValue 0 |> string |> printfn "%s"
        // Instead I used the 'Async.While' method that I defined above.

        let ConsumeReader = Async.While (fun () -> reader.ReadAsync() |> Async.AwaitTask)
        do! ConsumeReader (fun () -> reader.GetValue 0 |> string |> printfn "%s")
    }
    work |> Async.RunSynchronously
    0 // return an integer exit code
2个回答

12

你的代码中存在一个问题,即你正在使用 do! While predicateFn action 进行递归调用。这是有问题的,因为它不会转换为尾递归,所以可能会导致内存泄漏。正确的方法是使用 return! 而不是 do!

除此之外,你的代码工作得很好。但实际上你可以扩展 async 计算生成器,让你可以使用普通的 while 关键字。要做到这一点,你需要一个略微不同版本的 While

let rec While (predicateFn : unit -> Async<bool>) (action : Async<unit>) : Async<unit> = 
    async {
        let! b = predicateFn()
        if b then
            do! action
            return! While predicateFn action
    }

type AsyncBuilder with
    member x.While(cond, body) = Async.While cond body

在这里,主体也是异步的,并且它不是一个函数。然后我们向计算生成器添加了一个 While 方法(因此我们正在添加另一个重载作为扩展方法)。有了这个,您实际上可以编写:

 while Async.AwaitTask(reader.ReadAsync()) do // This is async!
     do! Async.Sleep(1000)   // The body is asynchronous too
     reader.GetValue 0 |> string |> printfn "%s"

2

我可能会和你做一样的事情。如果你能接受引用,你可以将其缩短为

let go = ref true
while !go do
  let! more = reader.ReadAsync() |> Async.AwaitTask
  go := more
  reader.GetValue 0 |> string |> printfn "%s"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接