ThreadPool を使用して、ネストされた非同期ループの次の単純な実装を取り上げます。
ThreadPool.SetMaxThreads(10, 10);
CountdownEvent icnt = new CountdownEvent(1);
for (int i = 0; i < 50; i++)
{
icnt.AddCount();
ThreadPool.QueueUserWorkItem((inum) =>
{
Console.WriteLine("i" + inum + " scheduled...");
Thread.Sleep(10000); // simulated i/o
CountdownEvent jcnt = new CountdownEvent(1);
for (int j = 0; j < 50; j++)
{
jcnt.AddCount();
ThreadPool.QueueUserWorkItem((jnum) =>
{
Console.WriteLine("j" + jnum + " scheduled...");
Thread.Sleep(20000); // simulated i/o
jcnt.Signal();
Console.WriteLine("j" + jnum + " complete.");
}, j);
}
jcnt.Signal();
jcnt.Wait();
icnt.Signal();
Console.WriteLine("i" + inum + " complete.");
}, i);
}
icnt.Signal();
icnt.Wait();
現在、このパターンを使用することはありません (開始時にデッドロックが発生します) が、スレッドプールで発生する可能性のある特定のデッドロックを示しています。つまり、ブロックしているスレッドがプール全体を消費した後、ネストされたスレッドが完了するのを待っている間にブロックすることです。
これのネストされた Parallel.For バージョンを使用して、同様に有害な動作を生成する潜在的なリスクがあるかどうか疑問に思っています:
Parallel.For(1, 50, (i) =>
{
Console.WriteLine("i" + i + " scheduled...");
Thread.Sleep(10000); // simulated i/o
Parallel.For(1, 5, (j) =>
{
Thread.Sleep(20000); // simulated i/o
Console.WriteLine("j" + j + " complete.");
});
Console.WriteLine("i" + i + " complete.");
});
明らかに、スケジューリング メカニズムははるかに洗練されています (そして、このバージョンがデッドロックするのはまったく見たことがありません) が、潜在的なリスクがまだそこに潜んでいる可能性があるようです。Parallel.For が使用するプールを枯渇させて、ネストされたスレッドに依存することでデッドロックを作成することは理論的に可能ですか? つまり、Parallel.For が遅延後にスケジュールされたジョブのバック ポケットに保持するスレッドの数に制限はありますか?