Asynchronous Task Run in a For Loop: System.ArgumentOutOfRangeException [duplicate] - c#

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.

Related

C# for loop with Task array IndexOutOfRangeException [duplicate]

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.

C# - Running a task in a for loop doesn't work [duplicate]

This question already has answers here:
How to tell a lambda function to capture a copy instead of a reference in C#?
(4 answers)
Closed 4 years ago.
I am trying to sum all the values of a byte array using 4 tasks. So I wrote a for loop in the main method with starting and running a new task each time it loops:
for (int i = 0; i < tasks.Length; i++)
{
Task.Run(() => Sum(i));
}
The parameter of the Sum method is portionNumber. In the Sum method, the first thing it execute is:
Console.WriteLine("I is " + i);
When I run it, it shows me:
I is 4
I is 4
I is 4
I is 4
However, if I wrote it like this:
Task.Run(() => Sum(0));
Task.Run(() => Sum(1));
Task.Run(() => Sum(2));
Task.Run(() => Sum(3));
The result prints:
I is 0
I is 1
I is 2
I is 3
My question is why? How can I run the 4 task using for loops instead of duplicating code?
Thanks alot.
you are running into a lambda capture common issue. you need
for (int i = 0; i < tasks.Length; i++)
{
var capi = i;
Task.Run(() => Sum(capi));
}
There is an excellent discusion from eric lippert somewhere on this
https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-over-the-loop-variable-considered-harmful/

C# Get NZEC error parsing input on HackerEarth

I want to learn C# so I started to use hackerearth and solve problems from their website but I got into some kind of problem. So I have the following code
using System;
namespace ConsoleApp6
{
class Program
{
static void Main(string[] args)
{
long N, i, answer = 1;
do
{
N = Convert.ToInt32(Console.ReadLine());
} while (N < 1 && N > 1000);
long[] A = new long[N];
for (i = 0; i < N; i++)
{
do
{
A[i] = Convert.ToInt32(Console.ReadLine());
} while (A[i] < 1 && A[i] > 1000);
}
for(i = 0; i < N; i++)
{
answer = (answer * A[i]) % (1000000007);
}
Console.WriteLine(answer);
}
}
}
When I compile it I get the correct answer and everything it's fine but when I submit it to hackerearth compiler it gives me the NZEC error. I thought I'm missing something since I just started C# some days ago so I wrote it again but in C++ and it gave me the maximum score on the website. I know that there might be some problems in my variable declarations since I didn't understand exactly how to read numbers and I hope you can help me solve this problem. Thank you!
Assuming you are stuck on the Find Product problem, as you've suspected, the input of the data is one line for N, and then one line for ALL N numbers that you need to multiply, separated by a space. You can parse the line of numbers quickly with LINQ (I would suggest you get stuck into LINQ as quickly as possible - this will get you away from the C++ imperative mindset).
How about:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApp6
{
class Program
{
static void Main(string[] args)
{
var N = Convert.ToInt32(Console.ReadLine()); // Less than 2^31 integers to be read
var A = Console.ReadLine() // Read the line of space delimited numbers
.Split(' ') // Split out by the separator
.Select(n => Convert.ToInt64(n)) // Parse each number to long
.ToArray(); // Convert to a materialized array
Debug.Assert(A.Length == N, "Site lied to us about N numbers");
long answer = 1; // or var answer = 1L;
for(var i = 0; i < N; i++)
{
answer = (answer * A[i]) % (1000000007);
}
Console.WriteLine(answer);
}
}
}
Some notes:
The do..while has no effect - they will always exit after one pass - this because a value cannot be < 1 and > 1000 simultaneously
Note that Convert.ToInt32 parses a 32 bit int. You've defined a long (which is ALWAYS 64 bit in C#, unlike C++), so this should be Convert.ToInt64
The problem does however constrain A[i] under 10 ^ 3, so A[] can be int[], although the product could be larger, so long or even System.Numerics.BigInteger can be used for the product.
NZEC is a site specific error - it means the app crashed with a non zero process ecit code. The site also prints the actual error and stack trace further down the page.
Since you say you want to learn C# (and not just convert C code to C#), you can also LINQify the final for loop which calculates the answer from the array using .Aggregate. Aggregate supports both a seeded overload (i.e. a left fold, as it allows the return type to differ), and an unseeded overload (i.e. reduce where the return type must be the same as the input enumerable). In your case, you don't actually need to seed the answer with 1L since it can be seeded with A[0] and the next multiplication will be with A[1] since any number multiplied by 1 will be number.
var answer = A.Aggregate((subtotal, next) => (subtotal * next) % (1000000007));

ArgumentOutOfRangeException. But it should not be there

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.

Parallel code throws OutOfRangeException

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.

Categories