ArgumentOutOfRangeException Using Tasks - c#

I'm getting an ArgumentOutOfRangeException when I'm really not sure why.
Task[] downloadTasks = new Task[music.Count];
for (int i = 0; i < music.Count; i++)
downloadTasks[i] = Task.Factory.StartNew(() => DownloadAudio(music[i], lstQueue.Items[i]));
Task.Factory.ContinueWhenAll(downloadTasks, (tasks) =>
{
MessageBox.Show("All the downloads have completed!",
"Success",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
});
The error occurs when the for loop runs when i = 1 and I'm not sure why it does this when I'm positive that music.Count = 1.
I always tried this approach as an alternative to the for loop and got the same exception:
int index = 0;
foreach (MusicFile song in music)
{
downloadTasks[index] = Task.Factory.StartNew(() => DownloadAudio(song, lstQueue.Items[index]));
index++;
}
Is there anything in the above code that might cause this?
I'm also not sure if this is relevant, but when I can accomplish the same thing using threads without any exception. It was only when I tried implementing tasks that this exception appeared.

This happens because you're passing StartNew a Lambda Expression, which implicitly captures your i variable. This effect is called Closure.
In order to get the proper behavior, you'll have to make a local copy of your index:
for (int i = 0; i < music.Count; i++)
{
var currentIndex = i;
downloadTasks[i] = Task.Factory.StartNew(() =>
DownloadAudio(music[currentIndex],
lstQueue.Items[currentIndex]));
}

In both instances, you are closing over the loop variable i in the first example, or your manually assigned index in the second.
What is happening is that the final value of i / index is used after the loop completion, which is when i++ has incremented beyond the size of the iterated array. (See also here)
Either capture the value of i inside the loop with an additional variable as per #Yuval, or alternatively, look at ways of coupling the two collections together, such that you do not need to iterate music and lstQueue independently, e.g. here we pre-combine the two collections into a new anonymous class:
var musicQueueTuples = music.Zip(lstQueue, (m, q) => new {Music = m, QueueItem = q})
.ToList();
// Which now allows us to use LINQ to project the tasks:
var downloadTasks = musicQueueTuples.Select(
mqt => Task.Factory.StartNew(
() => DownloadAudio(mqt.Music, mqt.QueueItem))).ToArray();
Task.Factory.ContinueWhenAll(downloadTasks, (tasks) => ...

Closures is your problem, where the variable i is being referenced by the lambada expression, thus it has access to i and always reads its value directly from the memory.
You can create a factory function that create task handlers. You can follow the following idea to solve the problem.
private Action CreateTaskHandler(int arg1)
{
return () => DownloadAudio(music[arg1], lstQueue.Items[arg1])
}
Task[] downloadTasks = new Task[music.Count];
for (int i = 0; i < music.Count; i++)
downloadTasks[i] = Task.Factory.StartNew(CreateTaskHandler(i));
}

Related

Modifying element inside a loop messes with the for loop

I'm using the following for loop to iterate through an IEnumerable:
for (int i = 0; i < items.Count(); i++)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var obj = items.ElementAt(i);
obj.TranslatedText = await Task.Run(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
progress.Report(i + 1);
await Task.Delay(DELAY);
}
The above code is skipping alternate elements. The loop is running only 4 times even though the count is 7.
I tried to replace the for loop with an equivalent foreach loop:
int current = 0;
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
item.TranslatedText = await Task.Run(() => Translator.Translate(item.EnglishText, "English", File.Lang));
progress.Report(++current);
await Task.Delay(DELAY);
}
It works fine, I can't find out what is different between those two.
I dug around a little bit more and found that if I remove the line
obj.TranslatedText = await Task.Run(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
from the first example, it executes just fine.
Am I not allowed to modify the contents of an IEnumerable? just curious.
UPDATE 1
I'm posting a reproducable example below.
https://pastebin.com/5ZXky7iX
Now we have a complete example, we can see what the problem is. The collection you're using for items is a query that depends on TranslatedText:
var source = collection.Where(x => string.IsNullOrEmpty(x.TranslatedText));
The action you take on obj invalidates the result of that query, for the item you're dealing with:
obj.TranslatedText = "something";
So in your for loop, initially all 10 Translation objects satisfy the condition, so Count() is 10. In the first iteration of the loop, you access the first element (element 0), and set obj.TranslatedText to "something".
Now in every iteration of the loop, you're counting "the current results of the query" - and that's now 9. You're then accessing the element by index in the current results of the query - so when i is 1, you're skipping the first match of the query and accessing the second match. But that isn't the second match in the original collection - it's the second match of the current query, which already skips the very first element, because you modified that to set the translation. So the original element index 1 is skipped in the second iteration of the loop, and you set the translated text for original element index 2 instead. Then Count() becomes 8, etc.
Using the foreach loop, you're only iterating over the query once - and while you're still invalidating the query condition for "the current element you're looking at", the query processing doesn't need to check that again anyway.
So either use the foreach loop, or if you want to access elements by index, you should materialize the query first. For example, you could use:
// Evaluate the query once, storing the results in a list
var list = items.ToList();
// Now you can operate on the list without worrying about the query
// being reevaluated.
for (int i = 0; i < list.Count; i++)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var obj = list[i];
obj.TranslatedText = await Task.Run(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
progress.Report(i + 1);
await Task.Delay(DELAY);
}

ASP.NET Core SaveChangesAsync does not save everything

PROBLEM:
If I put SaveChangesAsync outside of the loop, it changes only the last data which was put with _context.Add(attdef);
Why is that so?
First I thought it's because I have autoIncrement, but when I disabled it, It still did not work.
Using SaveChanges instead of SaveChangesAsync does not fix problem aswell.
But updating data works well.
GameController.cs
for (int i = 0; i < editViewModel.TowerAttack.Count; i++)
{
tower = _context.Tower.First(m => m.TowerId == editViewModel.TowerId[i]);
tower.Attack -= editViewModel.TowerAttack[i];
_context.Update(tower);
attdef.Id = 0; // AutoIncrement
attdef.Amount = attackSum;
_context.Add(attdef);
}
await _context.SaveChangesAsync();
I think you have declared attdef variable somewhere above and in the loop you're updating the same reference and adding it to the context. Due to this, you have single item adding in the context. The better way is to do it something like this
var attdefs = new List<Attdef>();
for (int i = 0; i < editViewModel.TowerAttack.Count; i++)
{
tower = _context.Tower.First(m => m.TowerId == editViewModel.TowerId[i]);
tower.Attack -= editViewModel.TowerAttack[i];
_context.Update(tower);
attdefs.Add(new AttacDef { id = 0, Amount = attackSum }) ;
}
_context.AddRange(attdefs); // don't remember exaxct syntaxt but this should be faster way
await _context.SaveChangesAsync();
Is attdef declared outside the loop? Are you just updating the same object with each loop? I would expect only the latest version of that object to be added if that's the case.
If you're trying to add several new objects, try declaring attdef within the loop so you're working with a new object each time.

ASP.NET adding data to class in loop

I have this code here:
for (int i = 0; i < reader.FieldCount; i++)
{
RedBlue item = new RedBlue();
if (reader.GetName(i).ToString().Contains("BID"))
{
item.baselinefinish = reader.GetValue(i).ToString();
}
if (reader.GetName(i).ToString().Contains("AID"))
{
item.actualenddate = reader.GetValue(i).ToString();
}
redBlue.Add(item);
}
What I am trying to do loop through data and add it to a class, but my problem is in my class I have two strings and I want to populate each string like this (first string gets the first item in the loop, the second string get the second item in the loop, and keep going like that, so instead of each one in the loop, for every two items in the loop add them to the string and continue on....I really hope this makes sense. Anyone know how I would accomplish this?
Currently what is happening, is it will add one of the strings to the class and then add the second string to a new class.
You can use Automapper and do something like this :
(adapted from what I remember of this framework, the docs here and your example)
// Configure AutoMapper
Mapper.Initialize(cfg =>
cfg.CreateMap<YourReaderClass, RedBlue>()
.ForMember(dest => dest.baselinefinish , opt => opt.MapFrom(src => src.BID))
.ForMember(dest => dest.actualenddate , opt => opt.MapFrom(src => src.AID))
// Perform mapping
RedBlue item = Mapper.Map<YourReaderClass, RedBlue>(reader);
You do the configuration once somewhere and then you can perform as many mapping you want. Of course, you have to manually indicate which field is mapped to which field with as many ForMember as you need.
EDIT
Actually, you could of course still do it without 3rd party, as you were thinking. To solve the specific problem with your method :
Currently what is happening, is it will add one of the strings to the
class and then add the second string to a new class.
(by the way, you mean instance of your class (object), not class )
Of course this happening, because you are creating new objects each time you iterate your loop !
If you do it like this, it should work :
// instantiate your object once, before the loop :
RedBlue item = new RedBlue();
for (int i = 0; i < reader.FieldCount; i++)
{
if (reader.GetName(i).ToString().Contains("BID"))
{
item.baselinefinish = reader.GetValue(i).ToString();
}
if (reader.GetName(i).ToString().Contains("AID"))
{
item.actualenddate = reader.GetValue(i).ToString();
}
}
// now you have one object named 'item' which should be what you want.
Not sure I follow, but right now you are creating a new object at every iteration and therefore each object will have only either string.
If your data is as you say, containing BID every second and AID every second element, as a poor mans solution, you could simply increment the i after adding the first string.
for (int i = 0; i < reader.FieldCount; i++)
{
RedBlue item = new RedBlue();
if (reader.GetName(i).ToString().Contains("BID"))
{
item.baselinefinish = reader.GetValue(i).ToString();
i++;
}
if (reader.GetName(i).ToString().Contains("AID"))
{
item.actualenddate = reader.GetValue(i).ToString();
}
redBlue.Add(item);
}
Or maybe I'm missing something?
Not sure if I follow your idea, but you can use even or odd index number to represent the first and second item on the loop.
Something like that:
for (int i = 0; i < reader.FieldCount; i++)
{
RedBlue item = null;
//if it's even index number
if(i % 2 == 0){
item = new RedBlue();
redBlue.Add(item);
if (reader.GetName(i).ToString().Contains("BID"))
item.baselinefinish = reader.GetValue(i).ToString();
}else{
if (reader.GetName(i).ToString().Contains("AID"))
item.actualenddate = reader.GetValue(i).ToString();
}
}

How do I create a Dictionary<int, EntityState>, and add values inside a for loop?

So, I hope this is simple. I'm coming up with a way to store disconnected entities (due to my case being quite peculiar), and for it to work, I'd like to create a Dictionary with those values inside a for loop.
But I'm getting "An item with the same key" has been added problem, which I do not know why.
I've tried the following:
Dictionary<int, EntityState> StateProduct = new Dictionary<int, EntityState>();
for (int s = 0; s < userProducts.Count; s++ ) //userProducts.Count had value of 3
{
StateProduct.Add(s, EntityState.Modified);
}
But I get the error:
In which:
I really really do not know what's going on..
Edit: Here is the complete code
var dbIboID = dbs.OrderDB.Where(x => x.OrderID == Order[0].OrderID).FirstOrDefault();
if(dbIboID.IboID != uid)
{
return false;
}
//2nd Step:
//2.0 Attach it. Yes I know it sets it as unchanged. But let me do the magic trick!!!
dbIboID.OrderProcess = Order.ToList(); //CHANGED
dbs.OrderDB.Attach(dbIboID);
//2.1 Extract original values from the database.
var originalProducts = dbs.OrderProcessDB.Where(x => x.OrderProcessID == Order[0].OrderProcessID).ToList();
var userProducts = Order.ToList();
//This is a dictionary which will be used to set all other entities with their correct states!
Dictionary<int, System.Data.Entity.EntityState> StateProduct = new Dictionary<int, System.Data.Entity.EntityState>();
//2.3 Find new added products. addedProducts = userProducts[key] - originalProducts[key]
if(userProducts.Count > originalProducts.Count)
{
for (int i = originalProducts.Count - 1; i < userProducts.Count; i++ )
{
StateProduct.Add(i, System.Data.Entity.EntityState.Added);
}
}
//2.3 Find Deleted products = originalProducts - userProducts. Do reverse of the addedProducts
else
{
for (int i = userProducts.Count - 1; i < originalProducts.Count; i++)
{
StateProduct.Add(i, System.Data.Entity.EntityState.Deleted);
}
}
//2.4 Find modified products modifiedProducts = [userProducts - addedProducts] different originalProducts
//This is not 100% fool proof. Because there will be times that I will always have a modification,
// when objects remained unchanged.
for (int s = 0; s < userProducts.Count; s++ )
{
StateProduct.Add(s, System.Data.Entity.EntityState.Modified);
}
//2.5 Painting Process:
for (int i = 0; i < dbIboID.OrderProcess.Count(); i++ )
{
dbs.DB.Entry(dbIboID.OrderProcess[i]).State = StateProduct[i];
}
The code as you have shown it should not produce that exception, because the dictionary was allocated immediately prior to the loop, and thus should be empty, and the items being added all are unique integers.
My guess is that the dictionary already had some values in it. If so, then using Add to set a value will throw an ArgumentException, since the value corresponding to that key can only be replaced, not added, for the Dictionary class only allows one value per key.
So, if you expect the dictionary not to already have a value for a key, and want an error exception to be thrown if it does, do:
StateProduct.Add(s, EntityState.Modified)
If you want to add or replace a value, do:
StateProduct[s] = EntityState.Modified;

task continuation messes up the references

So some of my code is called asynchronously like this
Task.Run(() => DoSomethingAsync());
DoSomethingAsync itself callss DoSomething2Async which looks like this
private void DoSomething2Async()
{
Something something = new Something(3);
Random rand = new Random();
for (int i = 0; i < 3; i++)
{
something.a = rand.Next(1000);
var task = new Task<int>(()
=> Calculate());
something.ContinueWith((t)
=> CalculateContinuation(something, task.Result));
task.Start();
}
MessageBox.Show("Do Something2 is done");
}
this is the Calculate() method
private int Calculate()
{
for (int i = 0; i < 100000000; i++)
{
var a = 5; // imitate some job
}
return new Random().Next();
}
and this is the CalculateContinuation() method
private void CalculateContinuation(Something something, int b)
{
MessageBox.Show(something.a.ToString(), b.ToString());
}
and this is the class Something
class Something : ICloneable
{
public int a;
public Something(int aa)
{
a = aa;
}
public object Clone()
{
Something clone = new Something(a);
return clone;
}
}
as you see Calculate is called 3 times and CalculateContinuation will also be called 3 times and I want to pass to the CalculateContinuation 2 parameters one would be the object configured before the call (in this case it is the instance of something) and the second would be the result of the Calculate method. now the thing is that the result of Calculate is different for every call (because it's random) and the something.a should be different for each call as well because its random too but every time the CalculateContinuation is hit something refers to the instance of Something which was configured in the final iteration of loop in DoSomething2Async. (if it would be hit before loop I think it would refer to the object configured at that time). I mean I get MessageBoxes where result of Calculate is different but something.a is not. I've been trying to solve this for two days I cant figure out what to do. I tried to pass the clone of something I tried to add somethings to collection and pass the last on each iteration, but nothing gives me the result desired. may be somebody had a problem like this. what is the solution in this situation. thanks in advance
EDIT:
Human readable Code
private void RegisterAllUsers()
{
Person person = new Person(string.Empty);
for (int i = 0; i < 3; i++)
{
person.Name = "Some name"; // different on each iteration
// create registration task
var registrationTask = new Task<bool>(()
=> RegisterUser(person));
// assign continue with, first parameter is person itself and second is the result of RegisterUse
registrationTask.ContinueWith((task)
=> RegistrationCallback(person, registrationTask.Result));
registrationTask.Start();
}
}
private bool RegisterUser(Person person)
{
// do registration stuff
return true; // or false if failes
}
private void RegistrationCallback(Person person, bool succeded)
{
// when this method executed the reference of person is whatever was set in RegisterAllUsers
// for the last time, but i want to have reference to the user which was configured on each iteration
// suppose 1st user is Steve 2nd is Bob and 3rd is Jack
// when this methid is hit the name of the user is Jack on all 3 callbacks but the succeded parameter
// is whatever the RegisterUser returned
// update registered user's status
}
RegisterAllUsers is call like this
Task.Run(() => RegisterAllUsers());
In relation to your edited part of code:
Person has only one instance: person. Then you start your loop with i. Loop fills up person.Name for each i and in the meantime it start a task. Unfortunately before task is being started, the foreeach loop is finished and the person instance contains only last assigned value. So you have three scheduled tasks and each of which takes one person instance that has only last values assigned to its Name since the Name depends on i which I have inferred from the problem description.
To fix the problem create the instance of Person for each task and use j variable inside your loop (closure issue):
for (int i = 0; i < 3; i++)
{
int j = i;
Person person = new Person(string.Empty);
person.Name = "Some name"; // depends on j this time rather on i
....

Categories