2015-07-26 9 views
6

Ich spiele herum mit SqlClient in F # und ich habe Schwierigkeiten mit SqlDataReader.ReadAsync. Ich versuche, die F # äquivalent'while' im asynchronen Berechnungsausdruck, wo die Bedingung async ist

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

zu tun Was ist der beste Weg, dies in F # zu tun? Unten ist mein komplettes Programm. Es funktioniert, aber ich würde gerne wissen, ob es einen besseren Weg gibt, es zu tun.

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 

Antwort

7

Es gibt ein Problem in Ihrem Code, ist, dass Sie einen rekursiven Aufruf tun mit
do! While predicateFn action. Dies ist ein Problem, weil es nicht zu einem Tail-Call wird und Sie Speicherlecks haben könnten. Der richtige Weg dazu ist return! statt do! zu verwenden.

Abgesehen davon funktioniert Ihr Code gut. Aber Sie können den async Berechnungsgenerator tatsächlich erweitern, damit Sie das normale Schlüsselwort while verwenden können. Um das zu tun, müssen Sie eine etwas andere Version von 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 

Hier ist der Körper auch asynchron und es ist keine Funktion. Dann fügen wir dem Berechnungsgenerator eine While-Methode hinzu (daher fügen wir eine weitere Überladung als Erweiterungsmethode hinzu). Mit diesem können Sie tatsächlich schreiben:

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

Ich würde wahrscheinlich das gleiche wie Sie tun. Wenn Sie jedoch Refs verspüren können, können Sie sie auf

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