2013-04-07 16 views
5

Typowym przykładem ilustrującym asynchroniczne przepływy pracy w F # jest pobieranie wielu stron jednocześnie. Jednym z takich przykładów jest podana na stronie: http://en.wikibooks.org/wiki/F_Sharp_Programming/Async_Workflows kod pokazany tutaj w przypadku zmiany ogniwem w przyszłości:Globalny stan i asynchroniczne przepływy pracy w języku 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() 

Co chciałbym wiedzieć, jak należy obsługiwać zmiany stanu globalnej, takich jak utrata połączenia sieciowego? Czy istnieje elegancki sposób na zrobienie tego?

Można sprawdzić stan sieci przed wykonaniem połączenia Async.Parallel, ale stan może się zmienić podczas wykonywania. Zakładając, że ktoś chciał zrobić, to wstrzymać wykonywanie, dopóki sieć nie będzie dostępna, a nie zawiedzie, czy istnieje funkcjonalny sposób, aby to zrobić?

Odpowiedz

4

Przede wszystkim, jest jeden problem na przykładzie - używa Async.Parallel uruchomić wiele operacji w równoległych ale operacji sami nie są realizowane jako asynchroniczne, więc nie będzie to uniknąć blokowania nadmierną liczbę wątków w wątku basen.

Asynchroniczny. aby kod całkowicie asynchroniczny, funkcje i downloadAndExtractLinksdownload powinny być asynchroniczny też tak, że można użyć AsyncDownloadString z 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 

ponowną próbą. Teraz, aby odpowiedzieć na pytanie - nie ma wbudowanego mechanizmu obsługi błędów, takich jak awaria sieci, więc będziesz musiał wdrożyć tę logikę samodzielnie. Jakie jest właściwe podejście zależy od twojej sytuacji. Jednym z powszechnych podejść jest ponowienie operacji określoną liczbę razy i odrzucenie wyjątku tylko wtedy, gdy się to nie uda, np. 10 razy. Możesz napisać to jako prymitywne, że bierze inną asynchroniczne workflow:

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

Następnie można zmienić główną funkcję zbudować przepływ pracy, który ponawia Pobierz 10 razy:

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

współdzielona stan. Innym problemem jest to, że Async.Parallel powróci dopiero po zakończeniu wszystkich pobrań (jeśli istnieje jedna wadliwa strona internetowa, będziesz musiał poczekać). Jeśli chcesz pokazać wyniki po ich powrocie, będziesz potrzebował czegoś bardziej wyrafinowanego.

Jedną z niezłych metod jest użycie agenta F # - utwórz agenta, który przechowuje dotychczas uzyskane wyniki i może obsłużyć dwie wiadomości - jedną, która dodaje nowy wynik, a druga zwraca bieżący stan. Następnie możesz uruchomić wiele zadań asynchronicznych, które wyślą wynik do agenta, aw osobnym asynchronicznym przepływie pracy możesz użyć odpytywania, aby sprawdzić bieżący stan (i na przykład zaktualizować interfejs użytkownika).

Napisałem MSDN series about agents, a także twoarticles dla developerFusion, które mają mnóstwo próbek kodu z agentami F #.

+0

Tom, chociaż bardzo lubię agentów F #, nie widzę, jak to jest programowanie funkcjonalne, takie jak Haskell. To, co wydaje się robić, to raczej traktowanie stanu (IO Monad w Haskell) jako czegoś, co ma zostać przekazane do funkcji, traktuje stan jako coś, co musi zostać zmutowane przez wielu agentów "jednocześnie" i rozstrzygnięte z komunikatem przekazywanym między agentami. – JonnyBoats

+2

Używanie agentów zdecydowanie nie działa tak jak Haskell.Szczerze mówiąc nie sądzę, że czysto funkcjonalne rozwiązania tego problemu są tak eleganckie i użyteczne. Współbieżność przekazywania komunikatów jest kolejnym przydatnym paradygmatem, który jest dostępny w języku F # - i myślę, że działa on bardzo dobrze dla współbieżnych procesów, które muszą koordynować. –

+0

To jest coś, co próbuję w tej chwili objąć. Po odkryciu FP przez takich jak Haskell (ale wciąż bardzo niedoświadczonych w tym), pokusą jest, aby przejść do całkowicie czystego podejścia w F #, too. Znalezienie odpowiedniej mieszanki paradygmatów będzie, jak sądzę, długim procesem uczenia się. – shambulator

Powiązane problemy