task continuation messes up the references - c#

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
....

Related

How to "return" multiple times with for loop?

Hopefully this post gives more clarity as to what I am trying to achieve.
Objective: I want to spawn 20 apples(that have an attached button) from a list at runtime. When the apples are clicked they will spawn a popup with information pertaining to the apple that was clicked.
What I'm doing currently: I am using a for loop to run through the list to spawn the apples. I currently have the following code:
public class AppleInventory : MonoBehaviour
{
[SerializeField] private ApplesScript applPrefab;
[SerializeField] private Transform applParent;
public ApplesScript CreateApples()
{
var appl = Instantiate(applPrefab, applParent);
for (int i = 0; i < apples.Count; i++)
{
appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
return appl;
}
}
The Problem: The problem is that when I use the for loop and click on the button,it returns the following error: ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. The popup information also does not update.
Code without for loop: The code works to spawn one apple when I remove the for loop and set the int i = to a specific number, like below. It will give the correct popup info for any number that "i" is set to. This lets me know that it is not the rest of the code that is the issue. This leads me to believe it is the "return" line along with the for loop that is the issue. It seems I may need to "return" for each iteration but I am unsure of how to go about doing this.
public ApplesScript CreateApples()
{
int i = 7;
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
return appl;
}
Thank you,
-
UPDATE
The fix was so simple. I just ended up creating a new method specifically for the for loop and it worked the way I wanted. My code now looks like this:
public void StarterOfApplesCreation()
{
for (int i = 0; i < apples.Count; i++)
{
CreateApples(i);
}
}
public void CreateApples(int i)
{
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
You have two options. The conventional option is to create all the items first and then return them all in some sort of list, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static Thing[] GetThings(int count)
{
var things = new Thing[count];
for (var i = 0; i < count; i++)
{
things[i] = new Thing { Number = i };
}
return things;
}
The more modern option is to use an iterator. It actually will return one item at a time. It has the limitation that you have to use the items there and then - you won't have random access like you would an array or the like - but it also has advantages, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static IEnumerable<Thing> GetThings(int count)
{
for (var i = 0; i < count; i++)
{
var thing = new Thing { Number = i };
yield return thing;
}
}
The result of an iterator will usually be used as the source for a foreach loop or a LINQ query. Note that you can always call ToArray or ToList on the result of an iterator if you do want random access in specific situations, but you still have the advantages of an iterator elsewhere. For instance, let's say that your method produces 1000 items and you want to find the first one that matches some condition. Using my first example, you would have to create all 1000 items every time, even if the first one was a match. Using an iterator, because the items are processed as they are created, you can abort the process as soon as you find a match, meaning that you won't unnecessarily create the remaining items.
Note that my examples use the following class:
public class Thing
{
public int Number { get; set; }
}
You can copy and paste the code into a Console app that doesn't use top-level statements. The bones of the code will still work with top-level statements, but you'll need to make a few other modifications.
Store each separate "appl" that gets instantiated in an Array, ie appls[i]=appl
Do this within the for loop.
If you think about it, by putting the line "return appl;" outside the for loop, you are only storing that last game object, not all of them. Thats why creating an array of gameobjects and assigning them within the loop may work for you.

Does expanding arraylist of objects make a new object?

Assume we have an array list of type Employe , does expanding it's length by 1 make a new object in the list ?
is the code in else statement correct? and is it recommended?
public void ModifierEmp(int c)
{
for(int i = 0; i < Ann.Count; i++)
{
if(Ann[i].Code == c)
{
Ann[i].saisie();
} else
{
i = Ann.Count + 1; //expanding arraylist ann
Ann[i].saisie(); //saisie a method for the user to input Employe infos
}
}
}
https://imgur.com/VfFHDKu "code snippet"
i = Ann.Count + 1;
The code above is not expanding the list: it is only setting your index variable (i) to have a new value.
If you wanted to make the list bigger, you would have to tell it which object to put into that new space you create. For example:
Ann.Add(anotherItem);
Of course, this gives you the ability to decide whether to add an existing item, create a new item (e.g. Ann.Add(new Something() { Code = c })), or even add a null value to the list (which is not usually a good idea).

ArgumentOutOfRangeException Using Tasks

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

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;

Checking for duplicates in an object array?

I have the following object array, created from a custom class Room. Before I add a new Room to the array I want to verify that the Roomname is not already in the array. Here is a sample of what I am trying:
private void btnAddRm_Click(object sender, EventArgs e)
{
Room[] roomArray = new Room[20];
test = txtName.text;
for (int i = 0; i < roomArray.length; i++)
{
if(test != roomArray[i].getRoomName())
{
addRoom();
}
}
}
GetRoomName() is the accessor that retrieves the RoomName from the class. I guess my question is why is this code not working?
Thank you
Your current code will add the room as soon as the room's name doesn't equal the new room.
Instead, first check that none of the rooms have the existing name and only then add it:
private void btnAddRm_Click(object sender, EventArgs e)
{
Room[] roomArray = new Room[20];
test = txtName.text;
bool exists = false;
for (int i = 0; i < roomArray.length; i++)
{
if (test == roomArray[i].getRoomName())
{
exists = true;
break;
}
}
if (!exists)
{
addRoom();
}
}
Your code fires addRoom() every time there is non-matching room in current array.
I think, you're looking for something like that:
if(!roomArray.Any(r => r.getRoomName() == test))
addRoom();
or using All method:
if(roomArray.All(r => r.getRoomName() != test))
addRoom();
You should finish looping through the entire array prior to adding the new room. With what you have if you have a new room name that doesn't match any existing rooms it'll call addRoom() 20 times.
Room[] roomArray = new Room[20];
test = txtName.text;
bool doesRoomExist = false;
for (int i = 0; i < roomArray.length; i++)
{
if (test == roomArray[i].getRoomName())
{
doesRoomExist = true;
break;
}
}
if (!doesRoomExist)
addRoom();
You can also condense you're for loop by using the Any extension method, you will need using System.Linq to do so.
if (!roomArray.Any(room => room.GetRoomName() == test))
addRoom();
Like others already stated your code does not make any sense. You create an array of rooms leaving all elements uninitialized (null). Then you check for existence of a room within that array. Also in your loop you "add" the new room each time an element has a different name. I have no clue how you want to add something to an array that is outside of the scope of your addRoom method. I hope this is not your actual code!
So first of all the room array should be created as a field of your class outside the scope of the btnAddRm_Click method.
Since you want to add rooms you should not use an array. Use a List<Room> instead. Otherwise you would need to know how many elements of your array are already initialized with a room. Also you would need to grow your array if the number exceeds the initial size of the array. This is exactly what List<Room> can do for you.
Your btnAddRm_Click event handler can then use LINQ to check for the existence of any room with the same name.
Please notice that you should use String.Compare(name1, name2) instead of name1 == name2.
private List<Room> _theRooms = new List<Room>();
private void btnAddRm_Click(object sender, EventArgs e)
{
if (!_theRooms.Any(r => string.Compare(r.Name, txtName.Text, StringComparison.CurrentCultureIgnoreCase) == 0))
{
addRoom();
}
}
Assuming that the room name is some kind of unique key for rooms you can also use a HashSet<Room> and then add the room anyway. HashSet will check for existence of the room. Your Room class however needs to overwrite Equals and GetHashCode then with Equals checking for equality of the room names.

Categories