2013-04-07 12 views
5

Un esempio comune utilizzato per illustrare flussi di lavoro asincroni in F # è il recupero di più pagine Web in parallelo. Un esempio è dato a: http://en.wikibooks.org/wiki/F_Sharp_Programming/Async_Workflows codice mostrato qui nel caso in cui i cambiamenti di collegamento per il futuro:Flussi di lavoro globali e asincroni 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() 

Quello che vorrei sapere è come si dovrebbe gestire cambiamenti di stato globale come la perdita di una connessione di rete? C'è un modo elegante per farlo?

Uno potrebbe verificare lo stato della rete prima di effettuare la chiamata Async.Parallel, ma lo stato potrebbe cambiare durante l'esecuzione. Supponendo che cosa si voleva fare era sospendere l'esecuzione fino a quando la rete era nuovamente disponibile piuttosto che fallire, c'è un modo funzionale per farlo?

risposta

4

Innanzitutto, c'è un problema con l'esempio - usa Async.Parallel eseguire più operazioni in parallele ma le operazioni stesse non vengono implementate come asincrono, quindi questo non eviterà il blocco eccessivo numero di fili nel filo piscina.

Asincrono. per rendere il codice completamente asincrona, le download e downloadAndExtractLinks funzioni dovrebbero essere asincrona anche, in modo che è possibile utilizzare AsyncDownloadString del WebClient:

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 

Nuovo tentativo. Ora, per rispondere alla domanda - non esiste un meccanismo integrato per la gestione di errori come errori di rete, quindi sarà necessario implementare questa logica da soli. Qual è l'approccio giusto dipende dalla tua situazione. Un approccio comune consiste nel riprovare l'operazione un certo numero di volte e lanciare l'eccezione solo se non riesce ad es. 10 volte. È possibile scrivere questo come un primitivo che prende altro flusso di lavoro asincrona:

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

Quindi è possibile modificare la funzione principale di costruire un flusso di lavoro che ritenta il download 10 volte:

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

stato condiviso. Un altro problema è che Async.Parallel verrà restituito solo al termine di tutti i download (se è presente un sito Web difettoso, sarà necessario attendere). Se vuoi mostrare i risultati mentre tornano, avrai bisogno di qualcosa di più sofisticato.

Un modo semplice per farlo è utilizzare l'agente F #: creare un agente che memorizzi i risultati ottenuti fino a quel momento e possa gestire due messaggi, uno che aggiunge un nuovo risultato e un altro che restituisce lo stato corrente. Quindi è possibile avviare più attività asincrone che invieranno il risultato all'agente e, in un flusso di lavoro asincrono separato, è possibile utilizzare il polling per verificare lo stato corrente (e ad esempio aggiornare l'interfaccia utente).

Ho scritto un MSDN series about agents e anche twoarticles per developerFusion che hanno un sacco di esempi di codice con agenti F #.

+0

Tom, anche se mi piacciono molto gli agenti di F #, non vedo come questa sia una programmazione funzionale come Haskell. Ciò che sembra fare è piuttosto che lo stato di trattamento (l'IO Monad in Haskell) come qualcosa da passare a una funzione, considera lo stato come qualcosa che deve essere mutato da più agenti "simultaneamente" e arbitrato con il passaggio di messaggi tra agenti. – JonnyBoats

+2

L'utilizzo di agenti non è sicuramente una programmazione funzionale come Haskell.Onestamente non penso che le soluzioni puramente funzionali al problema siano così eleganti e utili. La concorrenza di passaggio dei messaggi è solo un altro paradigma utile disponibile in F # - e penso che funzioni molto bene per i processi concorrenti che devono coordinarsi. –

+0

Questo è qualcosa che sto cercando di mettere alla prova in questo momento. Avendo scoperto FP attraverso il calibro di Haskell (ma ancora molto inesperto), la tentazione è di adottare un approccio completamente puro anche in F #. Trovare la giusta miscela di paradigmi sarà un lungo processo di apprendimento, penso. – shambulator

Problemi correlati