OmniThreadLibrary:如何检测所有递归调度(=池化)线程何时完成?

时间:2012-06-21 12:17:49

标签: multithreading delphi recursion threadpool omnithreadlibrary

假设我必须递归迭代存储在后台树结构中的项目,并且我想使用来自线程池的多个线程(每个“文件夹”节点一个线程)来遍历此树。我已经设法使用OmniThreadLibrary提供的几种不同的低级和高级方法来实现它。

但是,我还没想到的是如何正确检测扫描实际已完成,即每个最后一个叶子节点都已处理完毕。

我在网上找到了各种示例,要么检查GlobalThreadPool.CountExecuting + GlobalThreadPool.CountQueued <= 0还是使用IOmniTaskGroup.WaitForAll()。不幸的是,这些方法似乎都不适合我。检查总是过早地返回True,即仍有一些任务正在运行。我看过的所有例子都没有使用递归 - 而那些没有使用线程池的例子 - 这首先可能不是一个好的组合吗?

这是一个(非常)简化的示例代码片段,说明我目前是如何尝试这样做的:

procedure CreateScanFolderTask(const AFolder: IFolder);
begin
  CreateTask(ScanFolder)
    .SetParameter('Folder', AFolder)
    .Schedule();
end;

procedure ScanFolder(const ATask: IOmniTask);
var
  lFolder,
  lCurrentFolder: IFolder;
begin
  if ATaks.CancellationToken.IsSignalled then Exit;

  lCurrentFolder := ATask.Param['Folder'].AsInterface as IFolder;

  DoSomethingWithItemsInFolder(lCurrentFolder.Items);

  for lFolder in lCurrentFolder.Folders do
    begin
      if ATaks.CancellationToken.IsSignalled then Exit;
      CreateScanFolderTask(lFolder);
    end;
end;

begin
  GlobalOmniThreadPool.MaxExecuting := 8;
  CreateScanFolderTask(FRootFolder);

  // ??? wait for recursive scan to finish

  OutputResult();
end.

我尝试过的等待的一个示例实现是这样的(基于example found on About.com):

  while GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0 do
    Application.ProcessMessages;

但是这似乎总是在“根线程”完成后立即退出。即使我使用Sleep()添加人为延迟 - 调用它仍然总是过早退出。似乎在执行任务列表中的一个任务与在该任务中安排的任务被添加到排队任务列表之间发生“间隙”......

实际上,我不想等待扫描完成,而是非常喜欢使用事件处理程序(我也不想使用Application.ProcessMessages,因为我在无表格应用程序中也需要这样做)我已经尝试使用IOmniTaskControl.OnTerminated并使用TOmniEventMonitor,但是因为这些对于每个完成的任务都是火,我仍然需要检查当前的那个是否是最后一个再次归结为相同的问题如上。

或者是否有更好的方法来创建可避免此问题的任务?

3 个答案:

答案 0 :(得分:4)

一种简单的方法是自己计算“要处理的文件夹”。每增加一个值 创建文件夹任务的时间,并在每次处理文件夹时减少它。

var
  counter: TOmniCounter;

counter.Value := 0;

procedure ScanFolder(const ATask: IOmniTask);
var
  lFolder,
  lCurrentFolder: IFolder;
begin
  if ATaks.CancellationToken.IsSignalled then Exit;

  lCurrentFolder := ATask.Param['Folder'].AsInterface as IFolder;

  DoSomethingWithItemsInFolder(lCurrentFolder.Items);

  for lFolder in lCurrentFolder.Folders do
    begin
      if ATaks.CancellationToken.IsSignalled then Exit;
      counter.Increment;
      CreateScanFolderTask(lFolder);
    end;
  counter.Decrement;
end;

答案 1 :(得分:3)

我通常做的是统计所有已发行的&#39; folderScan&#39;反对并重新计算它们。

每次需要新的TfolderScan时,创建TfolderScan会为其调用工厂。工厂会增加受CS保护的任务数量&#39;以及创建TfolderScan。每次完成TfolderScan时,它都会调用&#39; OnComplete&#39;工厂的方法,减少受CS保护的任务数量&#39;。如果在&#39; OnComplete&#39;中,计数被递减为0,则不会有TfolderScan,并且整个搜索最完整。设法将计数减少到0的线程可以做任何表示完成的信号 - PostMessage()一个主表单或调用一个&#39; OnSearchComplete&#39;事件

答案 2 :(得分:0)

仅作为旁注:而不是检查(GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0)使用(not GlobalOmniThreadPool.IsIdle)。这隐藏了实现细节,效率更高。