0

I wanted to test parallel adding and removing to a ConcurrentDictionary. I have the following code in my test class:

    [TestMethod]
    public void T_Parallel_Removing
    {
        var management = new Management();

        List<Task> AddTasks = new List<Task>();
        List<Task> RemoveTasks = new List<Task>();

        var units = new List<Unit>();

        for (int i = 0; i < 30; i++)
        {
            var unit = new Unit();
            units.Add(unit);
            AddTasks.Add(Task.Run(() => AddUnit(management, unit)));
        }

        Task.WaitAll(AddTasks.ToArray());
    }

The AddUnit and RemoveUnit methods:

    public void AddUnit(Management m, Unit u,)
    {
        management .AddPlantUnit(u, "",);
    }

    public void RemoveUnit(Management m, Unit u)
    {
        m.RemovePlantUnit(u.Adress);
        Console.WriteLine(u.Adress);
    }

This works perfectly fine. But when i start Removing something odd happens (ignore the int areas, I somehow thought that they are a part of the problem, but they are not)

        for (int i = 0; i < 29; i++)
        {
            RemoveTasks .Add(Task.Run(() => RemoveUnit(management, units[i])));
        }

Here i get always the same address and the output if for every task the same.

But if I use following code:

         RemoveTakes.Add(Task.Run(() => RemoveUnit(management, units[0])));
         RemoveTasks.Add(Task.Run(() => RemoveUnit(management, units[1])));
         RemoveTasks.Add(Task.Run(() => RemoveUnit(management, units[2])));
         RemoveTasks.Add(Task.Run(() => RemoveUnit(management, units[3])));
         RemoveTasks.Add(Task.Run(() => RemoveUnit(management, units[4])));
         RemoveTasks.Add(Task.Run(() => RemoveUnit(management, units[5])));

It works without a problem. If I add some dummy Code or a Thread.Sleep(1) into the for loop it also works (every task gets a different address)

Can someone explain this behaviour?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Rolfant90
  • 13
  • 1
  • 3
  • If you change `Task.Run(() => RemoveUnit(management, units[i]))` to `Task.Run(() => Console.WriteLine(i))` what is written to the console? Is that what you expected? – mjwills Sep 14 '17 at 12:42
  • Without knowing about [closures](https://stackoverflow.com/q/428617/1997232) nearly any lambda in the loop = [problem](https://stackoverflow.com/q/16264289/1997232). – Sinatr Sep 14 '17 at 12:59

1 Answers1

2
for (int i = 0; i < 29; i++)
{
  RemoveTakes.Add(Task.Run(() => RemoveUnit(management, units[i])));
}

doesn't do what you think it does.

You should change it to:

for (int i = 0; i < 29; i++)
{
  var bob = i;
  RemoveTakes.Add(Task.Run(() => RemoveUnit(management, units[bob])));
}

With your original code, by the time the Task ran the value of i had changed. So you need to store that in a separate variable (bob - scoped inside the loop) so that the first Task gets 0, the next gets 1 etc.

Your issue is basically the same as this one (although that uses a foreach the principle is the same).

Note that if you have Resharper installed it should have highlighted this issue for you.

mjwills
  • 23,389
  • 6
  • 40
  • 63