2011-01-24 59 views
4

這很奇怪,因爲這是非常明顯的是,循環條件永遠不會導致異常C#從一個循環啓動線程拋出IndexOutOfBoundsException異常

Thread [] threads = new Thread[threadData.Length]; 
for (int i = 0; i < threadData.Length; i++) 
{ 
    threads[i]= new System.Threading.Thread(() => threadWork(threadData[i])); 
    threads[i].Start(); 
} 

它只是導致IndexOutOfBoundsException異常的threadData [I]

回答

8

這是正常的循環捕獲問題 - 你已經捕獲了循環變量,所以在線程實際啓動的時候,i是最終值,這是數組中的無效索引。該解決方案是創建一個新的變量的循環,捕獲,與其:

Thread[] threads = new Thread[threadData.Length]; 
for (int i = 0; i < threadData.Length; i++) 
{ 
    int copy = i; 
    threads[i]= new System.Threading.Thread(() => threadWork(threadData[copy])); 
    threads[i].Start(); 
} 

你可以閱讀更多有關這對埃裏克利珀的博客:part 1; part 2

就個人而言,我會考慮使用更多的List<T>,並儘可能使用foreach甚至LINQ。誠然,foreach不會解決這個問題,但它通常會更清潔IMO。

這裏有一個如何在LINQ已經做了一個例子:

List<Thread> threads = threadData.Select(x => new Thread(() => ThreadWork(x))) 
           .ToList(); 
foreach (Thread thread in threads) 
{ 
    thread.Start(); 
} 

還是採用了直板foreach循環,從每一個線程,當您去:

List<Thread> threads = new List<Thread>(); 
foreach (var data in threadData) 
{ 
    var dataCopy = data; 
    Thread thread = new Thread(() => ThreadWork(dataCopy)); 
    thread.Start(); 
    threads.Add(thread); 
} 
+0

感謝喬恩,我爲那個先回答的人標記了正確的答案。 – deadlock 2011-01-24 20:54:42

10

您已經捕獲過循環變量i這可能會導致每個線程最終執行時使用'i'的最後一個值,並從threadData中檢索數據。分配i對循環中的變量並使用它,例如:

Thread [] threads = new Thread[threadData.Length]; 

for (int i = 0; i < threadData.Length; i++) 
{ 
    int index = i; 
    threads[i]= new System.Threading.Thread(() => threadWork(threadData[index])); 
    threads[i].Start(); 
} 

埃裏克利珀對現象在這裏一個非常漂亮的文章:

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx

對於深入瞭解爲什麼會發生這種情況,考慮到線程將在未來某個未確定的點執行,也許在循環結束後執行。 Start表示線程應該啓動,它實際上是異步啓動,即不是立即啓動。鑑於此,我們可以看到,傳遞給Thread的lambda可能會在循環結束後執行得很好。那麼它如何能夠參考i

簡而言之,編譯器將創建一個可封裝i的助手類,然後用此助手類替換對i的引用。這允許lambda在循環範圍之外引用i。編譯器魔術一個很好的例子,但在這種情況下,具有非明顯的副作用,即它抓住了循環變量:

private class LambdaHelper 
    { 
     public int VarI { get; set; } 
    } 

    private static void SomeMethod() 
    { 
     LambdaHelper helper = new LambdaHelper(); 

     Thread[] threads = new Thread[threadData.Length]; 

     for (helper.VarI = 0; helper.VarI < data.Length; helper.VarI++) 
     { 
      threads[helper.VarI] = new Thread(() => ThreadWork(data[helper.VarI])); 
      threads[helper.VarI].Start(); 
     } 
    } 

在這裏我們可以看到,VarI代替的i使用。非顯而易見的副作用是當線程執行時,他們都看到共享的值,即VarI。如果線程在循環完成後啓動,它們將全部看到最大值i

解決的辦法是將i分配給循環內的臨時變量,如第一個代碼示例中所述。