C # Starting threads from Loop IndexOutOfBoundsException loop

This is very strange because it is very obvious that the loop condition will never throw an exception.

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(); } 

It simply raises IndexOutOfBoundsException for threadData [i]

+4
source share
2 answers

This is a common loop capture problem - you have captured a loop variable, so by the time the thread starts, thread i is the final value, which is an invalid index in the array. The solution is to create a new variable inside the loop and instead capture:

 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(); } 

Read more about this in Eric Lippert's blog: Part 1 ; part 2 .

Personally, I would like to use List<T> more, and using, if possible, foreach - or even LINQ. It is clear that foreach would not solve this problem, but overall it would be a cleaner IMO.

Here is an example of how you could do this in LINQ:

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

Or with a direct foreach loop, starting each thread:

 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); } 
+8
source

You grabbed the loop variable i , which can lead to the use of the last value of β€œi” when each thread ultimately executes and retrieves data from threadData . Assign i variable in a loop and use this instead, for example:

 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(); } 

Eric Lippert has a very good article on the phenomenon here:

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

To understand why this is happening, think that threads will run at some undefined point in the future, possibly after the loop ends. Start signals that the thread should start, it actually starts asynchronously, but not immediately. Given this, we can see that the lambda passed to Thread can perform well after the loop finishes. So how can it refer to i ?

In short, the compiler will create a helper class that can encapsulate i , and then replace the references to i with this helper class. This allows the lambda to have a reference to i outside the scope of the loop. A good example of compiler magic, but in this case has an unobvious side effect, i.e. it captures a loop variable:

  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(); } } 

Here we see that VarI used instead of i . An unobvious side effect is that when threads execute, they all see a common meaning, i.e. VarI . If threads start after the loop finishes, they will all see the maximum value of i .

The fix is ​​to assign i temporary variable inside the loop, as described in the first code example.

+10
source

Source: https://habr.com/ru/post/1336853/


All Articles