Paul Gildehaus
Paul Gildehaus

Reputation: 127

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.

Upvotes: 1

Views: 137

Answers (2)

Tobias Tengler
Tobias Tengler

Reputation: 7454

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

Upvotes: 2

Christos
Christos

Reputation: 53958

Regarding the first question:

what does the "action => action()" chunk of the loop do?

This is what is going to be called for each item in the actions list.

More formally it is called body of the Paraller.ForEach and it is the delegate that is invoked once per iteration. For more info, please have a look here.

Regarding the second question:

You should first capture the variable i, by using another variable inside the block of the for statement, before you use it in the lambda you create, like below:

for (int i = 0; i < 6; i++)     
{
    var j = i; 
    actions.Add(() => Function1("Word: " + j));
}

You can find a detailed explanation why you should do so here.

Upvotes: 2

Related Questions