2013-04-07 13 views
5

F#での非同期ワークフローの例として、複数のWebページを並行して取得する例があります。その一例がで与えられます。将来的には、リンクの変更の場合には、ここで示したhttp://en.wikibooks.org/wiki/F_Sharp_Programming/Async_Workflowsコード: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() 

私が知りたいのですが何が1は、このようなネットワーク接続の喪失などのグローバルな状態の変化を処理する方法ですか?これを行うエレガントな方法はありますか?

Async.Parallelコールを行う前にネットワークの状態をチェックできますが、実行中に状態が変わる可能性があります。ネットワークが失敗するのではなく、ネットワークが再び利用可能になるまで、実行を一時停止することを想定していたと仮定すると、これを行うための機能的な方法はありますか?

答えて

4

まず、例の1つの問題がある - それは並列に複数の操作を実行するためにAsync.Parallelを使用しますが、操作自体は非同期として実装されていないので、このスレッドでは、スレッドの過剰な数を遮断避けられないだろうプール。

非同期。あなたがWebClientAsyncDownloadStringを使用できるようにコードが完全に非同期にするために、downloaddownloadAndExtractLinks機能は、あまりにも非同期でなければなりません:

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 

再試行。ここでは、ネットワーク障害などのエラーを処理するためのメカニズムが組み込まれていないため、このロジックを自分で実装する必要があります。正しいアプローチはあなたの状況によって異なります。 1つの一般的なアプローチは、操作を一定回数再試行し、成功しなかった場合にのみ例外をスローすることです。 10回。そして、あなたは、ダウンロードに10回再試行したワークフローを構築する主な機能を変更することができます

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

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

共有状態あなたは他の非同期ワークフローをとるプリミティブとしてこれを書くことができます。もう1つの問題は、Async.Parallelがすべてのダウンロードが完了した後にのみ返されることです(障害のあるWebサイトが1つある場合は、待つ必要があります)。彼らが戻ってくるときに結果を表示したい場合は、より洗練されたものが必要です。

これを行ううれしい方法は、F#agentを使用することです。これまで得られた結果を保存し、新しい結果を追加するメッセージと現在の状態を返すメッセージの2つを処理できるエージェントを作成します。次に、結果をエージェントに送信する複数の非同期タスクを開始することができます。別の非同期ワークフローでは、ポーリングを使用して現在のステータスを確認できます(ユーザー・インターフェースの更新など)。

私はF#エージェントとのコードサンプルをたくさん持っているdeveloperFusionためMSDN series about agentsともtwoarticlesを書きました。

+0

Tom、私はF#エージェントが本当に好きですが、これはHaskellのような関数型プログラミングではありません。それは、状態(ハスケルのIO Monad)を関数に渡すものとして扱うことであり、状態を複数のエージェントによって「同時に」変更され、エージェント間のメッセージ受け渡しで仲裁されるものとして扱います。 – JonnyBoats

+2

エージェントの使用は間違いなくHaskellのような関数型プログラミングではありません。私は正直なところ、問題の純粋に機能的な解決策は、それがエレガントで便利だとは思わない。メッセージを渡す並行性は、F#で利用できるもう1つの有用なパラダイムです。そして、私は、それを調整する必要のある並行プロセスではうまく機能すると思います。 –

+0

これは私が現時点で頭を上げようとしていることです。 Haskellのようなものを通してFPを発見したが(それでも未熟ではあるが)、F#でも完全に純粋なアプローチをとることが誘惑である。パラダイムの正しいブレンドを見つけることは、長い学習プロセスになると私は思う。 – shambulator

関連する問題