Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
F# MailboxProcessor-告知何时停止?_F# - Fatal编程技术网

F# MailboxProcessor-告知何时停止?

F# MailboxProcessor-告知何时停止?,f#,F#,我现在正在玩MailboxProcessor。因此,我制作了一些代理,可以对计算机上的目录和所有子目录进行爬网,然后打印每个目录中的文件: let fileCollector = MailboxProcessor.Start(fun self -> let rec loop() = async { let! file = self.Receive() printfn "%s" file return! loo

我现在正在玩
MailboxProcessor
。因此,我制作了一些代理,可以对计算机上的目录和所有子目录进行爬网,然后打印每个目录中的文件:

let fileCollector =
  MailboxProcessor.Start(fun self -> 
    let rec loop() =
      async { let! file = self.Receive()
              printfn "%s" file
              return! loop() }
    loop()) 

let folderCollector = 
  MailboxProcessor.Start(fun self -> 
    let rec loop() =
      async { let! dir = self.Receive()
              do! Async.StartChild(
                    async { let! files = Directory.AsyncGetFiles dir
                            for z in files do fileCollector.Post z }) |> Async.Ignore
              return! loop() }
    loop())

let crawler =
  MailboxProcessor.Start(fun self ->
    let rec loop() =
      async { let! dir = self.Receive()
              folderCollector.Post dir
              do! Async.StartChild(
                    async { let! dirs = Directory.AsyncGetDirectories dir
                            for z in dirs do self.Post z }) |> Async.Ignore
              return! loop() }
    loop())

crawler.Post @"C:\Projects"

printfn "Done" // Message getting fired right away, due to the async stuff.
现在我如何判断
folderCollector
fileCollector
crawler
何时完成,以便在crawler成功爬网所有子目录并打印所有文件后调用最后的
printfn
语句

更新: 通过使用Tomas Petricek在中展示的技术,我成功地编写了以下代码:

let folders = new BlockingQueueAgent<string>(100)
let files = new BlockingQueueAgent<string>(100)

let rec folderCollector path =
  async { do! folders.AsyncAdd(path)
          do! Async.StartChild(
                  async { let! dirs = Directory.AsyncGetDirectories path
                          for z in dirs do
                            do! folderCollector z }) |> Async.Ignore }

let fileCollector =
  async { while true do
            let! dir = folders.AsyncGet()
            do! Async.StartChild(
                    async { let! fs = Directory.AsyncGetFiles dir
                            for z in fs do
                              do! files.AsyncAdd z }) |> Async.Ignore }

let rec printFiles() =
  async { let! file = files.AsyncTryGet(75)
          match file with
          | Some s -> 
            printfn "%s" s
            return! displayFiles()
          | None -> () }

let cts = new CancellationTokenSource()
Async.Start(folderCollector @"C:\Projects", cts.Token)
Async.Start(fileCollector, cts.Token)
Async.RunSynchronously(printFiles(), cancellationToken = cts.Token)

printfn "DONE!"
let folders = new BlockingQueueAgent<string option>(10)
let files = new BlockingQueueAgent<string option>(10)

let folderCollector path =
  async { let rec loop path = 
            async { do! folders.AsyncAdd(Some path)
                    let! dirs = Directory.AsyncGetDirectories path
                    do! [ for z in dirs -> loop z ] |> Async.Parallel |> Async.Ignore } 
          do! loop path 
          do! folders.AsyncAdd(None) }

let rec fileCollector() =
  async { let! dir = folders.AsyncGet 125
          match dir with
          | Some s -> 
            let fs = Directory.GetFiles s
            do! [ for z in fs -> printfn "%s" z; files.AsyncAdd(Some z) ] |> Async.Parallel |> Async.Ignore // <-- Fails silence if files are full
            do! fileCollector() // <-- unreachable
          | None -> printfn "Done!"; ()}
它很好用。但是,现在我无法跟踪
fileCollector
何时完成,因为它正在运行一系列异步计算,因此即使它在队列中变为“无”,它可能仍有一些工作要做。发生什么事了


更新: 我已将
fileCollector
修改为与
folderCollector
相同的“样式”,但问题仍然存在。修改后的版本:

let fileCollector() =
  async { let rec loop() = 
            async { let! dir = folders.AsyncGet 750
                    match dir with
                    | Some s -> 
                      let! fs = Directory.AsyncGetFiles s
                      do! [ for z in fs -> printfn "%A" z; files.AsyncAdd(Some z) ] 
                            |> Async.Parallel |> Async.Ignore 
                      return! loop()
                    | None -> printfn "Done!"; () }
          do! loop()
          printfn "after" // Never gets this far... 
          do! files.AsyncAdd(None) }

当F#agent完成时,没有内置的通知功能。其实很难说。代理(即使队列为空)尚未完成,因为它仍然可以从其他代理接收消息并重新开始工作

在您的示例中,这项工作是在所有三个代理的队列都为空时完成的。这可以使用
CurrentQueueLength
进行检查。这不是一个很好的解决方案,但它会起作用:

crawler.Post @"C:\Temp"
// Busy waiting until all queues are empty
while crawler.CurrentQueueLength <> 0 || folderCollector.CurrentQueueLength <> 0 ||
      fileCollector.CurrentQueueLength <> 0 do
    System.Threading.Thread.Sleep(10)
printfn "Done"
crawler.Post@“C:\Temp”
//忙着等待,直到所有队列都空了
而crawler.CurrentQueueLength 0 | | folderCollector.CurrentQueueLength 0||
fileCollector.CurrentQueueLength 0 do
系统线程线程睡眠(10)
打印fn“完成”
我认为更好的方法是以不同的方式构造代码——您实际上不需要使用代理来递归处理目录树。在您的版本中,目录的遍历(
crawler
agent)与在文件夹中查找文件(
folderCollector
)和处理结果(
fileCollector
)是并行完成的,因此您实际上实现了一个三步管道

只需使用
async
和用于存储即时处理结果的阻塞队列,就可以更轻松地实现管道。这我认为同样的方法也适用于你。检测管道处理何时结束应该更容易(在发送所有输入后,您可以发送一条指示完成的特殊消息,当消息到达管道的末尾时,您就完成了)


另一种选择是使用,这可能是解决此类问题的好模式(但目前在线上没有好的示例)。

回答第二个问题(来自评论)关于基于管道的更新版本-我认为您可以使用
BlockingQueueAgent
,并在生成完所有文件后使用值
None
(然后
None
值将通过管道传播,当它们获得
None
时,您可以结束所有工作流)

要做到这一点,您需要修改
folderCollector
,以实际检测它何时完成迭代。它没有经过测试,但以下应该可以工作(关键是您需要等待递归调用的完成):

由于
AsyncGet
,所有工作流都可能获得
None
。发生这种情况时,他们应该将
None
发送给管道中的下一个工作人员。最后一个可以在接收到
时终止:

let rec printFiles() =
  async { let! file = files.AsyncGet(75) // Note - now we use just AsyncGet
          match file with
          | Some s -> 
            printfn "%s" s
            return! displayFiles()
          | None -> () } // Completed processing all files

感谢您的快速回答:)-我之前确实看过您关于“图像处理”的文章,但是由于
BlockingQueueAgent
maxlength
作为参数,我只是跳过了它,因为我事先不知道文件/文件夹的数量。但听起来也有解决方法?@ebb-可以将
maxLength
参数设置为
Int32.MaxValue
-但实际上您可能不想这样做。它只是说,当项目数量达到最大计数时,队列将阻止不断向队列中添加项目的工作流(直到处理更多的项目)。当整个管道不能足够快地处理数据时,这用于避免队列中的数据过多。@ebb-。。。在文件处理示例中,您可能不希望创建一个包含磁盘上所有文件的队列。相反,您希望将一定数量的文件(例如100)排入队列并从队列中处理这些文件,而另一个进程在计数低于100时继续向队列中添加文件。啊,是的,挂起队列。在您的
displayPipelinedImages
函数中-您有
而true do…
-该函数将如何作为同步运行?如果您不介意,还请查看我的更新中的printFiles()函数。我不确定这是否是确定
文件中没有更多文件的“最佳”方法
BlockingQueueAgent…请查看我的帖子更新!:)-我不确定是否只有我一个人,但似乎在
BlockingQueueAgent
中有一个bug。。(我在你的博客文章中使用了与代理类型完全相同的代码)。@ebb-我认为你需要以与
folderCollector
类似的方式修改
fileCollector
(这样他们都可以跟踪end)。同样,使用
返回用于递归调用,而不是
do
(效率更高)(请参阅更新的帖子)我尝试过用与
folderCollector
类似的方式修改
filCollector
,但问题仍然存在。。。我一定忽略了一些非常简单的事情,但我看不出是什么。我想出来了!当您向
BlockingAgentQueue
添加某些内容,并且
队列的计数等于最大长度时,它只需将
let rec folderCollector path =
  let rec loop path = 
    async { do! folders.AsyncAdd(Some path)
            let! dirs = Directory.AsyncGetDirectories path
            do! [ for z in dirs do -> folderCollector z ] 
                |> Async.Parallel |> Async.Ignore }
  async { do! loop path
          do! folders.AsyncAdd(None) }
let rec printFiles() =
  async { let! file = files.AsyncGet(75) // Note - now we use just AsyncGet
          match file with
          | Some s -> 
            printfn "%s" s
            return! displayFiles()
          | None -> () } // Completed processing all files