2015-07-26 20 views
6

Sto giocando con SqlClient in F # e ho difficoltà con l'utilizzo di SqlDataReader.ReadAsync. Sto cercando di fare la F # equivalente di'while' nell'espressione di calcolo asincrono in cui la condizione è asincrona

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

Qual è il modo migliore per farlo in F #? Di seguito è il mio programma completo. Funziona, ma mi piacerebbe sapere se c'è un modo migliore per farlo.

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 

risposta

7

c'è un problema nel codice che è che si sta facendo una chiamata ricorsiva utilizzando
do! While predicateFn action. Questo è un problema perché non si trasforma in una coda di chiamata e quindi si potrebbe finire con perdite di memoria. Il modo giusto per farlo è utilizzare return! anziché do!.

A parte ciò, il codice funziona correttamente. Ma è possibile estendere il builder di calcolo async per consentire l'utilizzo della normale parola chiave while. Per fare questo, è necessaria una versione leggermente diversa di 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 

Qui, il corpo è anche asincrono e non è una funzione. Quindi aggiungiamo un metodo While al builder di calcolo (quindi stiamo aggiungendo un altro overload come metodo di estensione). Con questo, puoi scrivere:

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

Probabilmente farei lo stesso di te. Se si riesce a stomaco refs però, è possibile abbreviare a

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