2011-01-05 9 views
5

私は、ディスク上のすべてのディレクトリを非同期的にリストするプログラムをF#で作成しました。非同期タスクは、指定されたディレクトリ内のすべてのファイルをリストし、別々の非同期タスク(デーモン:Async.Startを使用して開始します)を作成してサブディレクトリを一覧表示します。彼らはすべて結果を中央のMailboxProcessorに伝えます。F#の非同期的なバリア

私の問題は、どのようにすべてのデーモンタスクが完了し、それ以上のファイルが到着しないことを検出するのですか?基本的には、私のトップタスクの(直接的かつ間接的な)子どもであるすべてのタスクの障壁が必要です。私はF#の非同期モデルでそれを見つけることができませんでした。

私が代わりに行ったことは、各タスクの開始と終了を登録する別のMailboxProcessorを作成することでした。アクティブカウントがゼロになると、私は完了です。しかし、私はその解決策に満足していません。その他の提案はありますか?

+0

解決策は何ですか?おそらくあなたは仕事を監督する1人のエージェントと、仕事を要求する多くのエージェントを使うことができます。作業者はディレクトリを探索して対応するディレクトリにファイルを取得し、結果を監督者に返信します(必ずしも最初のものと同じではない)。サブディレクトリを作業監督者に送信する – jlezard

答えて

7

Async.Parallelを試しましたか?つまり、各サブディレクトリAsync.Startではなく、サブディレクトリタスクをAsync.Parallel経由で単一の非同期に結合するだけです。その後、あなたはRunSynchronouslyであることができる(入れ子にされた)fork-joinタスクで終わり、最終結果を待つ。ここで

EDIT

は要点を示し、いくつかのおおよそのコード、そうでない場合は、完全な詳細です:

open System.IO 

let agent = MailboxProcessor.Start(fun mbox -> 
    async { 
     while true do 
      let! msg = mbox.Receive() 
      printfn "%s" msg 
    }) 

let rec traverse dir = 
    async { 
     agent.Post(dir) 
     let subDirs = Directory.EnumerateDirectories(dir) 
     return! [for d in subDirs do yield traverse d] 
       |> Async.Parallel |> Async.Ignore 
    } 

traverse "d:\\" |> Async.RunSynchronously 
// now all will be traversed, 
// though Post-ed messages to agent may still be in flight 

EDIT 2

ここでの回答を使用して待機バージョンです:

open System.IO 

let agent = MailboxProcessor.Start(fun mbox -> 
    async { 
     while true do 
      let! dir, (replyChannel:AsyncReplyChannel<unit>) = mbox.Receive() 
      printfn "%s" dir 
      replyChannel.Reply() 
    }) 

let rec traverse dir = 
    async { 
     let r = agent.PostAndAsyncReply(fun replyChannel -> dir, replyChannel) 
     let subDirs = Directory.EnumerateDirectories(dir) 
     do! [for d in subDirs do yield traverse d] 
       |> Async.Parallel |> Async.Ignore 
     do! r // wait for Post to finish 
    } 

traverse "c:\\Projects\\" |> Async.RunSynchronously 
// now all will be traversed to completion 
+1

アイデアはできるだけ早くリストを開始し、それを行いながら新しいサブディレクトリを発見しながら、新しいタスクの追加(および開始)を続けます。新しいサブディレクトリの検出は、ファイルのリストとインターリーブされます。 Async.Parallelを使ってすべてのタスクを結合する良い瞬間はありません。 –

+1

私は理解できません。 "foreach subdir、Async.デーモンを起動する" "[foreach subdirはデーモンを生成する] |> Async.Parallel"に変更し、 'すべてをキックオフする'という計算を返します。必要に応じて、コードを詳しく説明することができます。 – Brian

+0

デーモンには、階層の深さに応じて独自のデーモンなどがあります。ファイルはなく、ディレクトリだけが存在するとします。私があなたの解決策を正しく理解していれば、パラレルタスクを開始する前にそれらのリストがすべてリストアップされます。あれは正しいですか? –

1

タスクを開始/終了するときには、Interlockedを増減するだけで、ゼロになるとすべて完了します。私はMailboxProcessorsと同様のコードでこの戦略を使用しました。

+0

私は突然変異を避けようとしていました。 –

1

Task.Factory.StartNew()と0123を使用するほうがよい場合があります。

+0

私はできると思います。しかし、これはC#の解決策です。私の目標は、F#がマルチタスキングでより優れた/よりシンプルであるかどうかを調べることです。たぶんそれだけではありません:-( –

1

これはおそらく学習の練習ですが、すべてのファイルの怠惰なリストに満足しているようです。それは価値がある何のため

​​

(と私はこのようなものはF#のが、私は自宅で私と一緒に持っていない書籍、の全てであると思います)...上記ブライアンの答えから盗む、私が発見しましたF#での非同期ワークフローは、一般的なマルチタスキングはあまり試していませんが、 "困惑しやすい"並列問題に非常に役立ちます。

0

ちょうど解明のために:私は、チャペルでできることに似たよりよい解決策があるかもしれないと思った。そこには、 "同期"ステートメントがあります。これは、ステートメント内で生成されたすべてのタスクが完了するのを待つバリアです。ここでチャペルのマニュアルからの例です:

def concurrentUpdate(tree: Tree) { 
    if requiresUpdate(tree) then 
     begin update(tree); 
    if !tree.isLeaf { 
     concurrentUpdate(tree.left); 
     concurrentUpdate(tree.right); 
    } 
} 
sync concurrentUpdate(tree); 

「開始」文はAsync.StartとF#の「非同期」ブロックと少し似て並列に実行されるタスクを作成します。

+0

これを行うか、または非同期型を拡張する独自の計算式を作成する可能性があります。 – gradbot

関連する問題