2

バックグラウンドでツリー構造に格納されたアイテムを再帰的に反復する必要があり、スレッド プールから複数のスレッド (「フォルダー」ノードごとに 1 つのスレッド) を使用してこのツリーを歩きたいとしましょう。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を使用して人為的な遅延を追加しても、常に終了が早すぎます。実行中のタスクのリストから削除された 1 つのタスクと、そのタスク内でスケジュールされ、キューに入れられたタスクのリストに追加されるタスクとの間に「ギャップ」が発生しているようです...

実際には、スキャンが完了するのを待つ代わりに、イベント ハンドラーを使用することを非常に好みます (また、Application.ProcessMessagesフォームのないアプリケーションでもこれが必要になるため、使用したくありません)。IOmniTaskControl.OnTerminatedを使用しますTOmniEventMonitorが、これらは完了したすべてのタスクに対して起動するため、現在のタスクが最後のタスクであるかどうかを何らかの方法で確認する必要があり、これも上記と同じ問題になります。

または、この問題を回避するタスクを作成するためのより良い方法はありますか?

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 が完了するたびに、CS で保護された「taskCount」をデクリメントするファクトリの「OnComplete」メソッドが呼び出されます。「OnComplete」でカウントが 0 にデクリメントされると、TfolderScan が残っていない可能性があり、検索全体がほぼ完了します。カウントを 0 に減らすことができたスレッドは、完了を通知するために必要なことは何でも実行できます。つまり、PostMessage() メイン フォームを呼び出したり、'OnSearchComplete' イベントを呼び出したりします。

于 2012-06-21T15:08:29.960 に答える
0

補足として:(GlobalOmniThreadPool.CountExecuting + GlobalOmniThreadPool.CountQueued > 0)使用をチェックする代わりに(not GlobalOmniThreadPool.IsIdle)。これにより、実装の詳細が隠され、より効率的になります。

于 2012-06-22T10:46:06.417 に答える