【问题标题】:'while' in async computation expression where the condition is async条件为异步的异步计算表达式中的“while”
【发布时间】:2015-10-17 00:01:52
【问题描述】:

我在 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

【问题讨论】:

    标签: asynchronous f# sqldatareader sqlclient


    【解决方案1】:

    您的代码中存在一个问题,即您正在使用
    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
    

    这里,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】:

      我可能会和你一样。但是,如果您可以忍受 refs,则可以将其缩短为

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-01
        • 1970-01-01
        相关资源
        最近更新 更多