看看下面的天真實現一個嵌套的異步循環使用線程池:死鎖風險
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保留在延遲後計劃的作業的後面口袋中的線程數是否有限制?
是的,在前面的實現中,只有當你明確地減小了池的大小(以減少併發的I/O操作或者其他)時纔有可能。在後者中,我想你可以使用ParallelOptions分別調整每個循環的併發性,所以這兩種方法都是毫無意義的。有趣的是,它知道它在主線程中運行。 – silijon
WRT正在等待尚未安排的任務,我猜測這是假設調度程序在該線程上運行良好。如果後臺線程在需要在UI線程上運行的任務上執行Wait(),我希望它不會在該後臺線程上運行它:) –
@JamesManning是的,你說得對。這通過調用調度器的['TryExecuteTaskInline()'](http://msdn.microsoft.com/en-us/library/dd449178.aspx)來完成。調度程序可以通過返回'false'來拒絕內聯運行任務。 – svick