2013-04-07 11 views
5

Ein häufig verwendetes Beispiel zur Darstellung asynchroner Workflows in F # ist das parallele Abrufen mehrerer Webseiten. Ein solches Beispiel ist gegeben bei: http://en.wikibooks.org/wiki/F_Sharp_Programming/Async_Workflows-Code abgebildet, falls der Link in der Zukunft ändert:Globale Status- und Async-Workflows in F #

open System.Text.RegularExpressions 
open System.Net 

let download url = 
    let webclient = new System.Net.WebClient() 
    webclient.DownloadString(url : string) 

let extractLinks html = Regex.Matches(html, @"http://\S+") 

let downloadAndExtractLinks url = 
    let links = (url |> download |> extractLinks) 
    url, links.Count 

let urls = 
    [@"http://www.craigslist.com/"; 
    @"http://www.msn.com/"; 
    @"http://en.wikibooks.org/wiki/Main_Page"; 
    @"http://www.wordpress.com/"; 
    @"http://news.google.com/";] 

let pmap f l = 
    seq { for a in l -> async { return f a } } 
    |> Async.Parallel 
    |> Async.Run 

let testSynchronous() = List.map downloadAndExtractLinks urls 
let testAsynchronous() = pmap downloadAndExtractLinks urls 

let time msg f = 
    let stopwatch = System.Diagnostics.Stopwatch.StartNew() 
    let temp = f() 
    stopwatch.Stop() 
    printfn "(%f ms) %s: %A" stopwatch.Elapsed.TotalMilliseconds msg temp 

let main() = 
    printfn "Start..." 
    time "Synchronous" testSynchronous 
    time "Asynchronous" testAsynchronous 
    printfn "Done." 

main() 

Was ich möchte wissen, ist, wie man Veränderungen in der globalen Zustand wie der Verlust einer Netzwerkverbindung behandeln sollte? Gibt es eine elegante Möglichkeit, dies zu tun?

Man könnte den Status des Netzwerks vor dem Aufruf von Async.Parallel überprüfen, aber der Status könnte sich während der Ausführung ändern. Angenommen, man wollte die Ausführung pausieren, bis das Netzwerk wieder verfügbar war, anstatt fehlzuschlagen. Gibt es dafür einen funktionalen Weg?

Antwort

4

Zunächst einmal gibt es ein Problem mit dem Beispiel - es Async.Parallel verwendet mehrere Operationen in parallel aber die Operationen selbst werden nicht umgesetzt als asynchron ausgeführt werden, so dass dies nicht verhindern, dass eine übermäßige Anzahl von Threads im Thread blockiert Schwimmbad.

Asynchron. Um den Code vollständig asynchron zu machen, die download und downloadAndExtractLinks Funktionen zu asynchron sein sollten, so dass Sie AsyncDownloadString der WebClient verwenden:

let asyncDownload url = async { 
    let webclient = new System.Net.WebClient() 
    return! webclient.AsyncDownloadString(System.Uri(url : string)) } 

let asyncDownloadAndExtractLinks url = async { 
    let! html = asyncDownload url 
    let links = extractLinks html 
    return url, links.Count } 

let pmap f l = 
    seq { for a in l -> async { return! f a } } 
    |> Async.Parallel 
    |> Async.RunSynchronously 

Retrying. Nun, um die Frage zu beantworten - es gibt keinen eingebauten Mechanismus für die Behandlung von Fehlern wie Netzwerkfehler, so dass Sie diese Logik selbst implementieren müssen. Was ist der richtige Ansatz, hängt von Ihrer Situation ab. Ein üblicher Ansatz besteht darin, die Operation eine bestimmte Anzahl von Malen erneut zu versuchen und die Ausnahme nur dann auszulösen, wenn sie z.B. 10 mal. Sie können dies als eine primitive schreiben, die andere asynchrone Workflow nimmt:

let rec asyncRetry times op = async { 
    try 
    return! op 
    with e -> 
    if times <= 1 then return (reraise e) 
    else return! asyncRetry (times - 1) op } 

Dann können Sie die Hauptfunktion ändern, um einen Workflow zu erstellen, die den Download 10-mal wiederholt:

let testAsynchronous() = 
    pmap (asyncRetry 10 downloadAndExtractLinks) urls 

Gemeinsamer Zustand. Ein weiteres Problem ist, dass Async.Parallel erst zurückkehrt, wenn alle Downloads abgeschlossen sind (wenn es eine fehlerhafte Website gibt, müssen Sie warten). Wenn Sie die Ergebnisse zeigen möchten, wie sie zurückkommen, werden Sie etwas anspruchsvolleres brauchen.

Ein guter Weg, dies zu tun ist mit F # -Agent - Erstellen Sie einen Agenten, der die bisher erzielten Ergebnisse speichert und zwei Nachrichten behandeln kann - eine, die neues Ergebnis und eine andere, die den aktuellen Zustand zurückgibt. Dann können Sie mehrere asynchrone Tasks starten, die das Ergebnis an den Agenten senden, und in einem separaten asynchronen Workflow können Sie mithilfe von Abfragen den aktuellen Status überprüfen (und z. B. die Benutzeroberfläche aktualisieren).

Ich schrieb eine MSDN series about agents und auch twoarticles für developerFusion, die eine Menge Code-Beispiele mit F # -Agenten haben.

+0

Tom, während ich F # -Agenten wirklich mag, sehe ich nicht, wie das funktionale Programmierung wie Haskell ist. Es scheint eher so zu sein, als behandle der Staat (die IO Monad in Haskell) etwas, das an eine Funktion übergeben werden soll, behandelt den Staat als etwas, das von mehreren Agenten "gleichzeitig" mutiert und mit Botschaften zwischen Agenten vermittelt wird. – JonnyBoats

+2

Die Verwendung von Agenten ist definitiv keine funktionale Programmierung wie Haskell.Ich glaube ehrlich gesagt nicht, dass die rein funktionalen Lösungen für das Problem so elegant und nützlich sind. Message-Passing Concurrency ist nur ein weiteres nützliches Paradigma, das in F # verfügbar ist - und ich denke, es funktioniert wirklich gut für gleichzeitige Prozesse, die koordiniert werden müssen. –

+0

Dies ist etwas, was ich versuche, mich im Moment zu bewegen. Nachdem ich FP über Leute wie Haskell entdeckt habe (aber immer noch sehr unerfahren), ist die Versuchung, auch in F # einen völlig reinen Ansatz zu wählen. Die richtige Mischung von Paradigmen zu finden, wird ein langer Lernprozess sein, denke ich. – shambulator