'while' 在条件为异步的异步计算表达式中
'while' in async computation expression where the condition is async
我正在尝试在 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
您的代码中存在一个问题,即您正在使用
进行递归调用
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"
我可能会和你一样。不过,如果你能忍受 refs,你可以将其缩短为
let go = ref true
while !go do
let! more = reader.ReadAsync() |> Async.AwaitTask
go := more
reader.GetValue 0 |> string |> printfn "%s"
我正在尝试在 F# 中使用 SqlClient,但在使用 SqlDataReader.ReadAsync
时遇到了困难。我正在尝试做相当于
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
您的代码中存在一个问题,即您正在使用
进行递归调用
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"
我可能会和你一样。不过,如果你能忍受 refs,你可以将其缩短为
let go = ref true
while !go do
let! more = reader.ReadAsync() |> Async.AwaitTask
go := more
reader.GetValue 0 |> string |> printfn "%s"