This question already has answers here:
IndexOutOfRangeException exception when using tasks in for loop in C#
(1 answer)
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
Closed 3 years ago.
im starting 10 tasks to get result from web api. Im get IndexOutOfRangeException on offsets array. But how its posible.
Actually 'i' variable canot be greater or equal that 10.
Can anyone help with this?
For loop not working corectly?
times = 10;
Task[] tasks = new Task[times];
int[] offsets = new int[times];
for (int i = 0; i < times; i++)
{
offsets[i] = offset;
tasks[i] = Task.Run(() => SearchLocalByQuery(query, offsets[i], (i + 1)));
offset += Limit;
}
Task.WaitAll(tasks);
i = 10,
i cannot be 10 in for loop off this example.
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
i cannot be 10 in for loop off this example.
It can be in the lambda expression. This expression:
() => SearchLocalByQuery(query, offsets[i], (i + 1))
captures the variable i, and will evaluate that variable value whenever it is executed. Task.Run returns immediately but won't necessarily have started executing the delegate yet - so it's entirely possible that the loop has moved onto the next iteration, or completed, before i is evaluated.
The simple fix for this is to declare a local variable to capture i inside the loop:
for (int i = 0; i < times; i++)
{
int copy = i;
offsets[i] = offset;
// Make sure we only use copy within the lambda expression
tasks[i] = Task.Run(() => SearchLocalByQuery(query, offsets[copy], copy + 1));
offset += Limit;
}
Now there'll be a fresh copy variable for each iteration of the loop, and that variable will never change its value - so the lambda expression will execute using "the value of i for that iteration of the loop" regardless of what the current value of i is.
Related
This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 2 years ago.
I'm fairly new to programming and decided to test out an idea I had involving asynchronous execution of array sorting tasks. I'm almost completely new to asynchronous programming and am running into an error that seems to only be explainable via some sort of strange asynchronous... stuff... occurring.
When I step through the following code to debug it, it works perfectly. When I allow it to run freely, however, I run into an Argument Out Of Range Exception: I becomes greater than allArrays.Count - 1, and the program fails. However, the
if (i >= allArrays.Count) Console.WriteLine($"i is too high! i = {i}");
line is never executed before it fails. Can someone explain this to me, and help possibly propose a solution to this issue?
Thank you!
//iterate while allArrays contains multiple arrays to be merged.
while (allArrays.Count > 1)
{
if (allArrays.Count % 2 != 0)//if it's not even, we add one and make it even, so every array has a partner!
{
allArrays.Add(new int[0]);
}
for (int i = 0; i < allArrays.Count - 1; i+=2)
{
Console.WriteLine($"i is {i}");
if (i >= allArrays.Count) Console.WriteLine($"i is too high! i = {i}");
mergeTasks.Add(Task.Run(() => Merge(allArrays[i], allArrays[i + 1])));
}
await Task.WhenAll(mergeTasks);
//empty the list of smaller arrays
allArrays.Clear();
//add results to all arrays then empty mergeTasks
mergeTasks.ForEach(r => allArrays.Add(r.Result));
mergeTasks.Clear();
}
Add temporary variable to store i and use it in lambda for task:
for (int i = 0; i < allArrays.Count - 1; i+=2)
{
var tmp = i; // create copy of current i
Console.WriteLine($"i is {i}");
if (i >= allArrays.Count) Console.WriteLine($"i is too high! i = {i}");
// use tmp here instead of i:
mergeTasks.Add(Task.Run(() => Merge(allArrays[tmp], allArrays[tmp + 1])));
}
Task.Run accepts lambda. To use a variable from outside scope it will create so called closure. For the for loop the same closure instance is used, so the captured value can change, so it is possible for it to change to the last value(which is i > allArrays.Count - 1) before your last task calls Merge(allArrays[i], allArrays[i + 1]) resulting in the exception in the question.
You can try to "validate" this behavior for example with:
mergeTasks.Add(Task.Run(() => {
if (i >= allArrays.Count) Console.WriteLine($"i is too high! i = {i}");
return Merge(allArrays[tmp], allArrays[tmp + 1]);
}));
You can dive deeper with this article or this question.
This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
What is an "index out of range" exception, and how do I fix it? [duplicate]
(1 answer)
Closed 6 years ago.
So I am working on a game, aside from a game engine, and I have a for loop to detect collisions with all objects in a list of Panels
Here is the code:
for (int x = 1; x <= 2; x++)
{
if (player.obj.Bounds.IntersectsWith(walls[x].Bounds))
{
MessageBox.Show("COLLIDING");
}
}
Currently there are only two objects added to the list called walls
And everytime I go to run it tells me Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index I don't know what to do, am I setting up the if statement wrong?
I just tried taking it out of the for loop and replaced x with 0, and when I touched that object it said I was colliding, so I know I didn't set up the if statement wrong.
As you may or may not know, arrays start at 0 in the index, so your array should be like so.
for (int x = 0; x < 2; x++)
{
if (player.obj.Bounds.IntersectsWith(walls[x].Bounds))
{
MessageBox.Show("COLLIDING");
}
}
If there are 2 objects in walls the loop needs not go to x = 2
for (int x = 0; x < 2; x++)
{
if (player.obj.Bounds.IntersectsWith(walls[x].Bounds))
{
MessageBox.Show("COLLIDING");
}
}
As Arrays start at index 0
You have two elements in the walls[] array means they ware located in walls[0] and walls[1]( Since .Net arrays are following 0 based indexing) and hence walls[2] is out of bound; So you should start the loop with 0 to get the first element and loop up to 2. But i strongly recommend you to use length of walls instead of 2
for (int x = 0; x < walls.Length; x++)
{
if (player.obj.Bounds.IntersectsWith(walls[x].Bounds))
{
MessageBox.Show("COLLIDING");
}
}
So I have and method like this.
var someColletion = _someService.GetSomeCollection(someParam);
var taskCollection = new Task<double>[someCollection.Count];
for (int i = 0; i < taskCollection.Length; i++)
{
// do some stuff on the i-th element of someCollection and taskCollection
// and start the i-th task
}
Task.WaitAll(taskCollection);
double total = 0;
for (int i = 0; i < taskCollection.Length; i++)
{
// get the result of each task and sum it in total variable
}
return total;
the case is when it comes into first for loop and the number of elements in both collections are suppose 1 the ArgumentOutOfRangeException is being thrown and then AggregateException is being thrown on Task.WaitAll() because the i becomes 1 (I don't know why but it does) and when it tries to access the i-th (second) element in array that contains just one element, this happens. But there is more to this. If i set a break point before first loop and go step by step then this thing does not happen. when i becomes one the cycle ends. and everything's okay. now the method I provided above is called by an ASP.NET MVC Controller's Action which itself is called Asynchronously (by ajax call) suppose 3 times. and out of this three just one executes correctly other two do the thing I said above (if not breakpointed). I think that this problem is caused by ajax call most probably because when I breakpoint it stops other calls from executing. Can anyone suggest anything ?
I suspect you're using i within the first loop, capturing it with a lambda expression or anonymous method, like this:
for (int i = 0; i < taskCollection.Length; i++)
{
taskCollection[i] = Task.Run(() => Console.WriteLine(i));
}
If that's the case, it's the variable i which is being captured - not the value of the variable for that iteration of the loop. So by the time the task actually executes, it's likely that the value of i has changed. The solution is to take a copy of the iteration variable within the loop, in a separate "new" variable, and capture that in the anonymous function instead:
for (int i = 0; i < taskCollection.Length; i++)
{
int copy = i;
taskCollection[i] = Task.Run(() => Console.WriteLine(copy));
}
That way, each task captures a separate variable, whose value never changes.
This question already has answers here:
creating new threads in a loop
(2 answers)
Closed 9 years ago.
In C#, if I execute
for (int i = 0;i < 10;i++)
new Thread(() => Console.Write(i)).Start();
I will possibly get 0223557799, that's strange, since i is a int, I think it should be copied before the thread starts.
Closures are your problem here.
Basically, instead of grabbing the value when you create the lambda (in the loop), it grabs it when it needs it. And computers are so fast that by the time that happens, it's already changed. It can't go through the whole loop, but it goes through some of it.
Here's a fix:
for (int i = 0; i < 10; i++)
{
var n = i;
new Thread(() => Console.Write(n)).Start();
}
Because Start() returns immediately, i++ happens before the thread gets a chance to print i to the console. I believe that a workaround is to create a local copy of the int, then print that:
for (int i = 0;i < 10;i++) {
int j = i;
new Thread(() => Console.Write(j)).Start();
}
What basically is happening is this:
You want to start a thread that prints the value of i.
The thread starts.
The code operating in the thread gets the value if i. Note that the value of i can be changed by now.
The value of i gets printed. But no guarantees to get a logical output.
Copy the value of i into another variable first and then print that value. The other answers provide enough samplecode.
Your lambda will be translated into set of the method and context class which will handle a refference to the i.
I would use the built in .NET parallelism support Task Parallelism
You won't have to worry about managing the threads it's done for you.
Example your code converted to the Parallelism libraries.
Parallel.For(0, 10, (i, state) =>
{
Console.WriteLine(i);
});
Does anyone know how why this code returns out of range exception?
For example if the leastAbstractions List instance has count == 10, the loop will execute 11 times finishing with i = 10 and returning this exception.
for (int i = 0; i < leastAbstractions.Count; i++)
{
Task.Factory.StartNew((object state) =>
{
this.Authenticate(new HighFragment(leastAbstractions[i])).Reactivate();
}, TaskCreationOptions.PreferFairness);
}
Your loop isn't actually executing 11 times - it's only executing 10 times, but i == 10 by the time some of those tasks execute.
It's the normal problem - you're capturing a loop variable in a lambda expression. Just take a copy of the counter, and capture that instead:
for (int i = 0; i < leastAbstractions.Count; i++)
{
int copy = i;
Task.Factory.StartNew((object state) =>
{
this.Authenticate(new HighFragment(leastAbstractions[copy]))
.Reactivate();
}, TaskCreationOptions.PreferFairness);
}
That way, when your task executes, you'll see the current value of the "instance" of copy that you captured - and that value never changes, unlike the value of i.
See Eric Lippert's blog posts on this: part 1; part 2.