[英]AgrumenNullException in ThreadPool.QueueUserWorkItem
[英]Unexpected behaviour for ThreadPool.QueueUserWorkItem
請檢查下面的代碼示例:
public class Sample
{
public int counter { get; set; }
public string ID;
public void RunCount()
{
for (int i = 0; i < counter; i++)
{
Thread.Sleep(1000);
Console.WriteLine(this.ID + " : " + i.ToString());
}
}
}
class Test
{
static void Main()
{
Sample[] arrSample = new Sample[4];
for (int i = 0; i < arrSample.Length; i++)
{
arrSample[i] = new Sample();
arrSample[i].ID = "Sample-" + i.ToString();
arrSample[i].counter = 10;
}
foreach (Sample s in arrSample)
{
ThreadPool.QueueUserWorkItem(callback => s.RunCount());
}
Console.ReadKey();
}
}
此示例的預期輸出應類似於:
Sample-0 : 0
Sample-1 : 0
Sample-2 : 0
Sample-3 : 0
Sample-0 : 1
Sample-1 : 1
Sample-2 : 1
Sample-3 : 1
.
.
.
但是,當您運行此代碼時,它將顯示以下內容:
Sample-3 : 0
Sample-3 : 0
Sample-3 : 0
Sample-3 : 1
Sample-3 : 1
Sample-3 : 0
Sample-3 : 2
Sample-3 : 2
Sample-3 : 1
Sample-3 : 1
.
.
.
我可以理解,線程執行的順序可能不同,因此計數不會以循環方式增加。 但是,我無法理解為什么所有ID
都顯示為Sample-3
,而執行顯然是相互獨立的。
不同的對象是否與不同的線程一起使用?
這是舊的修改后的閉包問題。 您可能希望查看: Threadpools -類似問題的可能線程執行順序問題 ,以及Eric Lippert的博客文章Closing over loop variable被認為對於理解該問題有害 。
從本質上講,你所獲得的lambda表達式是捕獲變量 s
而不是聲明lambda的變量值 。 因此,隨后到變量的值作出的改變到委托可見。 RunCount
方法將運行的Sample
實例將取決於委托實際執行時變量s
(其值)引用的實例 。
此外,由於委托(編譯器實際上重用了相同的委托實例)正在異步執行,因此無法保證每次執行時這些值是什么。 您目前看到的是foreach
循環在任何委托調用之前在主線程上完成(預期 - 在線程池上調度任務需要時間)。 因此, 所有工作項最終都會找到循環變量的“最終”值。 但這無法保證; 嘗試在循環內插入一個合理持續時間的Thread.Sleep
,您將看到不同的輸出。
通常的解決方法是:
捕獲'copy'變量而不是lambda中的循環變量。
foreach (Sample s in arrSample) { Sample sCopy = s; ThreadPool.QueueUserWorkItem(callback => sCopy.RunCount()); }
現在每個工作項“擁有”循環變量的特定值。
在這種情況下的另一個選擇是通過不捕獲任何東西完全避開問題:
ThreadPool.QueueUserWorkItem(obj => ((Sample)obj).RunCount(), s);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.