F# F中的异步势垒#

F# F中的异步势垒#,f#,functional-programming,synchronization,multitasking,F#,Functional Programming,Synchronization,Multitasking,我用F#编写了一个程序,异步列出磁盘上的所有目录。异步任务列出给定目录中的所有文件,并创建单独的异步任务(守护进程:我使用async.start启动它们)来列出子目录。它们都将结果传送给中央邮箱处理器 我的问题是,如何检测所有守护进程任务都已完成,并且不会有更多文件到达。基本上,我需要一个障碍,所有任务(直接和间接)都是我的首要任务的子任务。我在F#的异步模型中找不到类似的东西 我所做的是创建一个单独的MailboxProcessor,在这里注册每个任务的开始和结束。当活动计数变为零时,我就完成

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

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


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

您可以在开始/结束任务时使用递增和递减,当它变为零时就可以全部完成。我在MailboxProcessor的类似代码中使用了此策略。

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

编辑

下面是一些大致代码,它显示了要点(如果不是全部细节的话):

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 
opensystem.IO
让代理=MailboxProcessor.Start(有趣的mbox->
异步的{
尽管如此
let!dir,(replyChannel:AsyncReplyChannel)=mbox.Receive()
printfn“%s”目录
replyChannel.Reply()
})
让rec遍历dir=
异步的{
设r=agent.PostAndAsyncReply(趣味replyChannel->dir,replyChannel)
让subDirs=目录。枚举目录(dir)
do![对于子曲面中的d,不产生遍历d]
|>Async.Parallel |>Async.Ignore
do!r//等待Post完成
}
以同步方式遍历“c:\\Projects\\”|>Async.RunSynchronously
//现在,所有这些都将被遍历完成

您最好只使用
Task.Factory.StartNew()
Task.WaitAll()
这可能是一个学习练习,但似乎您会对所有文件的惰性列表感到满意。从上面Brian的答案中偷取。。。(我想所有的F#books里都有类似的东西,我在家里没有)


值得一提的是,我发现F#中的异步工作流对于“非常简单”的并行问题非常有用,尽管我没有尝试过太多的通用多任务处理。

只是为了澄清一下:我认为可能有一个更好的解决方案类似于Chapel中的解决方案。这里有一个“sync”语句,一个等待语句中生成的所有任务完成的屏障。以下是《教堂手册》中的一个示例:

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”块。

我试图避免变异。其想法是尽快开始列出,并在这样做和发现新的子目录时,不断添加(和启动)新任务。新子目录的发现与文件列表交织在一起。使用Async.Parallel组合所有任务不是一个好时机。我不明白-假设您现在有了“foreach subdir,Async.Start a daemon”,将其大约更改为“[foreach subdir do yield daemon]|>Async.Parallel”,并返回“启动一切”的计算。如果需要,我可以更详细地解释代码。问题是守护进程有自己的守护进程等等,这取决于层次结构的深度。假设没有文件,只有目录。如果我正确理解了您的解决方案,您甚至可以在开始并行任务之前将它们全部列出。是这样吗?我不这么认为,请查看我最近编辑的答案,看看是否有帮助。是的,为了解决这个问题,您可以使用
PostAndAsyncReply
而不是
Post
。例如,代理可以在完成处理后进行回复,而异步内部遍历将使等待回复成为并行执行的事情之一,因此整个过程都会阻塞,直到完成为止。您的解决方案有什么问题?也许您可以使用一个代理来监督要做的工作,也可以使用许多代理来请求工作。工作人员得到一个要浏览的目录,然后在相应的目录中获取文件,然后将结果发回给主管(不一定与第一个相同),并将要浏览的子目录发布给工作主管。我想我可以。但这是一个C解决方案,我的目标是找出F在多任务处理方面是否更好/更简单。也许它不是:-(您可以创建自己的计算表达式来实现这一点,或者扩展异步类型。
def concurrentUpdate(tree: Tree) {
    if requiresUpdate(tree) then
        begin update(tree);
    if !tree.isLeaf {
        concurrentUpdate(tree.left);
        concurrentUpdate(tree.right);
    }
}
sync concurrentUpdate(tree);