複数のスレッドを実行し、別のスレッドが終了すると新しいスレッドを開始する
-
25-09-2019 - |
質問
多くのケースがあるアプリケーションがあります。各ケースには多数の複数ページの tif ファイルがあります。TF ファイルを PDF ファイルに変換する必要があります。ファイルが非常に多いため、変換プロセスをスレッド化できると考えました。現在、プロセスを一度に 10 回の変換 (つまり 10 回のトレッド) に制限しています。1 つの変換が完了すると、別の変換が開始されます。
これが私が使用している現在のセットアップです。
private void ConvertFiles()
{
List<AutoResetEvent> semaphores = new List<AutoResetEvet>();
foreach(String fileName in filesToConvert)
{
String file = fileName;
if(semaphores.Count >= 10)
{
WaitHandle.WaitAny(semaphores.ToArray());
}
AutoResetEvent semaphore = new AutoResetEvent(false);
semaphores.Add(semaphore);
ThreadPool.QueueUserWorkItem(
delegate
{
Convert(file);
semaphore.Set();
semaphores.Remove(semaphore);
}, null);
}
if(semaphores.Count > 0)
{
WaitHandle.WaitAll(semaphores.ToArray());
}
}
これを使用すると、WaitHandle.WaitAll() または WaitHandle.WaitAny() 配列パラメータの長さが 65 を超えてはいけないという例外が発生することがあります。このアプローチの何が間違っているのでしょうか?どうすれば修正できるでしょうか?
解決
あなたが書いたことにはいくつか問題があります。
まず、スレッドセーフではありません。AutoResetEvents の配列を追加、削除、待機する複数のスレッドがあります。List の個々の要素には個別のスレッドでアクセスできますが、すべての要素を追加、削除、またはチェックするもの (WaitAny 呼び出しなど) は、ロック内で行う必要があります。
2 番目に、コードが一度に 10 個のファイルしか処理しないという保証はありません。リストのサイズがチェックされる時点から、新しい項目が追加される時点までのコードは、複数のスレッドが通過できるようにオープンされています。
3 番目に、QueueUserWorkItem で開始されたスレッドが同じファイルを変換する可能性があります。ループ内で fileName をキャプチャしないと、ファイルを変換するスレッドは、QueueUserWorkItem を呼び出したときに fileName に含まれていた値ではなく、実行時に fileName に含まれていた値を使用します。
このコードプロジェクトの記事は、あなたがやろうとしていることの正しい方向を示すはずです。 http://www.codeproject.com/KB/threads/SchedulingEngine.aspx
編集:
var semaphores = new List<AutoResetEvent>();
foreach (String fileName in filesToConvert)
{
String file = fileName;
AutoResetEvent[] array;
lock (semaphores)
{
array = semaphores.ToArray();
}
if (array.Count() >= 10)
{
WaitHandle.WaitAny(array);
}
var semaphore = new AutoResetEvent(false);
lock (semaphores)
{
semaphores.Add(semaphore);
}
ThreadPool.QueueUserWorkItem(
delegate
{
Convert(file);
lock (semaphores)
{
semaphores.Remove(semaphore);
}
semaphore.Set();
}, null);
}
個人的には、私はこのようにはしないと思います...しかし、あなたが持っているコードを操作すると、これはうまくいくはずです。
他のヒント
あなたのように見えますが、
続行するWAITANY機能をトリガーハンドルを削除する必要がありますif(semaphores.Count >= 10)
{
int index = WaitHandle.WaitAny(semaphores.ToArray());
semaphores.RemoveAt(index);
}
だから、基本的に、私は削除します。
semaphores.Remove(semaphore);
スレッドからのコールとシグナルイベントを削除し、それが動作するかどうかを確認するために、上記を使用します。
たぶん、あなたは非常に多くのイベントを作成するべきではないのですか?
// input
var filesToConvert = new List<string>();
Action<string> Convert = Console.WriteLine;
// limit
const int MaxThreadsCount = 10;
var fileConverted = new AutoResetEvent(false);
long threadsCount = 0;
// start
foreach (var file in filesToConvert) {
if (threadsCount++ > MaxThreadsCount) // reached max threads count
fileConverted.WaitOne(); // wait for one of started threads
Interlocked.Increment(ref threadsCount);
ThreadPool.QueueUserWorkItem(
delegate {
Convert(file);
Interlocked.Decrement(ref threadsCount);
fileConverted.Set();
});
}
// wait
while (Interlocked.Read(ref threadsCount) > 0) // paranoia?
fileConverted.WaitOne();