我在F#中编写了一个程序,它异步列出磁盘上的所有目录。异步任务列出给定目录中的所有文件,并创建单独的异步任务(守护程序:我使用Async.Start启动它们)以列出子目录。它们都将结果传递给中央邮箱处理器。
我的问题是,如何检测所有守护程序任务已完成,并且不会再有文件到达。基本上我需要一个障碍来完成我的顶级任务的(直接和间接)孩子的所有任务。我在F#的异步模型中找不到类似的东西。
我所做的是创建一个单独的MailboxProcessor,我在其中注册每个任务的开始和终止。当活动计数变为零时,我就完成了。但我对这个解决方案不满意。还有其他建议吗?
答案 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”块。