バックグラウンドでツリー構造に格納されたアイテムを再帰的に反復する必要があり、スレッド プールから複数のスレッド (「フォルダー」ノードごとに 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
が、これらは完了したすべてのタスクに対して起動するため、現在のタスクが最後のタスクであるかどうかを何らかの方法で確認する必要があり、これも上記と同じ問題になります。
または、この問題を回避するタスクを作成するためのより良い方法はありますか?