Search code examples
c#multithreadingparallel.foreach

Can someone explain the Parallel.ForEach loop logic happening here?


I am just trying to learn the different ways of doing threading/tasks and I wanted a way to dynamically change the tasks that were being done and I was pointed to the Parallel.ForEach loop. I made a little example program and I have a few questions.

public void StartTest()
{
    List<Action> actions = new List<Action>();
    for (int i = 0; i < 6; i++)     
    {
        actions.Add(() => Function1("Word: " + i));
    }
    Parallel.ForEach(actions, new ParallelOptions
    {
        MaxDegreeOfParallelism = 2
    }, action => action());

    Console.WriteLine("Finished. \nTime Taken: " + total.ToString(@"dd\.hh\:mm\:ss"));
    Console.Read();
}


private void Function1(string word)
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(word + " |  Task Id: " + Task.CurrentId + " |   " + i);
    }
    Console.WriteLine(word + " ----- Completed.");
}

So my first question is what does the "action => action()" chunk of the loop do? I understand what lambdas are but I really am just not following this.

My second question is why is this the output?

Word: 6 | Task Id: 3 | 0

Word: 6 | Task Id: 3 | 1

Word: 6 | Task Id: 3 | 2

Word: 6 | Task Id: 3 | 3

Word: 6 | Task Id: 3 | 4

Word: 6 ----- Completed.

Word: 6 | Task Id: 3 | 0

Word: 6 | Task Id: 3 | 1

Word: 6 | Task Id: 3 | 2

Word: 6 | Task Id: 3 | 3

Word: 6 | Task Id: 3 | 4

Word: 6 ----- Completed.

Word: 6 | Task Id: 3 | 0

Word: 6 | Task Id: 3 | 1

Word: 6 | Task Id: 3 | 2

Word: 6 | Task Id: 3 | 3

Word: 6 | Task Id: 3 | 4

Word: 6 ----- Completed.

Word: 6 | Task Id: 3 | 0

Word: 6 | Task Id: 3 | 1

Word: 6 | Task Id: 3 | 2

Word: 6 | Task Id: 3 | 3

Word: 6 | Task Id: 3 | 4

Word: 6 ----- Completed.

Word: 6 | Task Id: 3 | 0

Word: 6 | Task Id: 3 | 1

Word: 6 | Task Id: 3 | 2

Word: 6 | Task Id: 2 | 0

Word: 6 | Task Id: 2 | 1

Word: 6 | Task Id: 2 | 2

Word: 6 | Task Id: 2 | 3

Word: 6 | Task Id: 2 | 4

Word: 6 ----- Completed.

Word: 6 | Task Id: 3 | 3

Word: 6 | Task Id: 3 | 4

Word: 6 ----- Completed.

Finished.

Time Taken: 00.00:00:00

Why is every single number 6? I understand how the threading is working, but not the passing / referencing of the parameters.

So those are my two questions. Any help would be fantastic. I searched google for a while and could not find any documentation that made sense to me.


Solution

  • public void StartTest()
    {
        var actions = new List<Action>();
        for (int i = 0; i < 6; i++)
        {
            // you can't pass 'i' directly to the Action here,
            // because 'i' is in the scope of where the Action is executed
            // and since the parameter of the Action is first evaluated at the 
            // execution of the Action if you were to put 'i' it checks what value
            // 'i' has at the current point in time and since the for-loop finished
            // already it is always going to be 6
            var word = $"Word: {i}";
            actions.Add(() => Function1(word));
        }
    
        // you could interpret the lambda 'action => action()' as
        // foreach(var action in actions)
        //     action();
        // so it essentially tells you what it's going to do for each
        // Action Item in the Action-Collection you passed as Parallel.ForEach's first parameter,
        // while 'action' represents the "current" item
        Parallel.ForEach(actions, new ParallelOptions{MaxDegreeOfParallelism = 2}, action => action());
    
        Console.WriteLine("Finished. \nTime Taken: " + total.ToString(@"dd\.hh\:mm\:ss"));
        Console.Read();
    }
    
    
    private void Function1(string word)
    {
        for (int i = 0; i < 5; i++)
            Console.WriteLine(word + " |  Task Id: " + Task.CurrentId + " |   " + i);
    
        Console.WriteLine(word + " ----- Completed.");
    }