2

假设我必须在后台递归迭代存储在树结构中的项目,并且我想使用线程池中的多个线程(每个“文件夹”节点一个线程)遍历这棵树。我已经设法使用 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.

我尝试过的等待的一个示例实现是这样的(基于在 About.com 上找到的示例):

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

但这似乎总是在“根线程”完成后立即退出。即使我使用Sleep()-calls 添加人为延迟,它仍然总是过早退出。似乎在从执行任务列表中删除的一项任务与在该任务中安排的要添加到排队任务列表中的任务之间存在“差距”......

实际上,与其等待扫描完成,我更喜欢使用事件处理程序(另外,我宁愿不使用Application.ProcessMessages,因为我在无表单应用程序中也需要它)并且我已经尝试过两者IOmniTaskControl.OnTerminated并且使用 a TOmniEventMonitorbut 因为这些火为每一个完成的任务我仍然需要以某种方式检查当前的是否是最后一个,这再次归结为与上述相同的问题。

或者有没有更好的方法来创建可以避免这个问题的任务?

4

3 回答 3

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;
于 2012-06-21T15:08:14.973 回答
3

我通常做的是将所有发出的“folderScan”对象计数出来,然后重新计数。

每次需要一个新的 TfolderScan 时,创建 TfolderScan 都会为其调用一个工厂。工厂增加一个受 CS 保护的“taskCount”以及创建 TfolderScan。每次完成 TfolderScan 时,它都会调用工厂的“OnComplete”方法,以减少受 CS 保护的“taskCount”。如果在“OnComplete”中,计数减为 0,则可能没有 TfolderScan,并且整个搜索最完整。设法将计数减为 0 的线程可以做任何需要发出完成信号的信号 - PostMessage() 主窗体或调用“OnSearchComplete”事件。

于 2012-06-21T15:08:29.960 回答
0

作为旁注:而不是检查(GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0)use (not GlobalOmniThreadPool.IsIdle)。这隐藏了实现细节并且更有效。

于 2012-06-22T10:46:06.417 回答