F#中的异步屏障

时间:2011-01-05 20:28:32

标签: f# functional-programming synchronization multitasking

我在F#中编写了一个程序,它异步列出磁盘上的所有目录。异步任务列出给定目录中的所有文件,并创建单独的异步任务(守护程序:我使用Async.Start启动它们)以列出子目录。它们都将结果传递给中央邮箱处理器。

我的问题是,如何检测所有守护程序任务已完成,并且不会再有文件到达。基本上我需要一个障碍来完成我的顶级任务的(直接和间接)孩子的所有任务。我在F#的异步模型中找不到类似的东西。

我所做的是创建一个单独的MailboxProcessor,我在其中注册每个任务的开始和终止。当活动计数变为零时,我就完成了。但我对这个解决方案不满意。还有其他建议吗?

5 个答案:

答案 0 :(得分:7)

您是否尝试过使用Async.Parallel?也就是说,而不是Async.Start每个子目录,只需通过Async.Parallel将子目录任务合并为一个异步。然后你得到一个(嵌套的)fork-join任务,你可以RunSynchronously并等待最终结果。

修改

这是一些近似代码,显示了要点,如果不是完整的细节:

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

编辑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 :(得分:1)

您可以在开始/结束任务时使用Interlocked递增和递减,并在它变为零时完成所有操作。我在与MailboxProcessors类似的代码中使用了这种策略。

答案 2 :(得分:1)

您最好只使用Task.Factory.StartNew()Task.WaitAll()

答案 3 :(得分:1)

这可能是一个学习练习,但似乎你会对所有文件的懒惰列表感到满意。从上面的Brian的答案中窃取......(我觉得所有的F#书都是这样的,我家里没有这本书)

open System.IO

let rec traverse dir =
seq {
    let subDirs = Directory.EnumerateDirectories(dir)
    yield dir 
    for d in subDirs do
        yield! traverse d

}

对于它的价值,我发现F#中的Async工作流对于“令人尴尬的简单”并行问题非常有用,尽管我没有尝试过多次一般的多任务处理。

答案 4 :(得分:0)

只是为了澄清:我认为可能有一个更好的解决方案,类似于Chapel中可以做的事情。在那里你有一个“sync”语句,一个等待在语句中产生的所有任务完成的屏障。以下是Chapel手册中的一个例子:

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

“begin”语句创建一个并行运行的任务,有点类似于使用Async.Start的F#“async”块。