Tono Nam
Tono Nam

Reputation: 36048

Pass non reference value to Task

I often get strange resulst when passing int variables to tasks such as in this example:

List<List<object>> ListToProcess = new List<List<object>>();

// place some lists in list to process
foreach (var temp in Foo)
    ListToProcess.Add(temp);

foreach (var tempArray in ListToProcess)
{
    // initialize each list in ListToProcess                    
}

int numberOfChunks = ListToProcess.Count; 
Task[] tasks = new Task[numberOfChunks];
for (int counter = 0; counter < numberOfChunks; counter++)
{
    tasks[counter] = Task.Factory.StartNew(() =>
        {
            // counter is always = 5  why?   <---------------------------
            var t = ListToProcess[counter];                        
        });
}

How can I solve this problem?

Upvotes: 0

Views: 267

Answers (4)

Kenneth Ito
Kenneth Ito

Reputation: 5261

Since variables are captured, you can solve this by redeclaring a new variable in each loop.

for (int counter = 0; counter < numberOfChunks; counter++)
{
    int localCounter = counter;
    tasks[localCounter] = Task.Factory.StartNew(() =>
        {
            // counter is always = 5  why?   <---------------------------
            var t = ListToProcess[localCounter];                        
        });
}

Upvotes: 0

Tono Nam
Tono Nam

Reputation: 36048

    for (int counter = 0; counter < numberOfChunks; counter++)
    {
        var referenceVariable = new{val=counter};
        tasks[counter] = Task.Factory.StartNew(() =>
            {
                var t = ListToProcess[referenceVariable.val];                        
            });
    }

Upvotes: 0

Kendall Frey
Kendall Frey

Reputation: 44326

This is known as a closure. You are not using the value of the variable, you are using the variable itself. When the code executes, it uses the value at the time of execution, not the value when the Task was defined.

To fix this issue, I believe you would do something like this:

for (int counter = 0; counter < numberOfChunks; counter++)
{
    int cur = counter;
    tasks[counter] = Task.Factory.StartNew(() =>
    {
        // counter is always = 5  why?   <---------------------------
        var t = ListToProcess[cur];                        
    });
}

Upvotes: 3

yamen
yamen

Reputation: 15618

There is no guarantee as to when the 'counter' variable in the Action block of StartNew will be accessed. What is likely to happen is that all 5 values are looped through, and the tasks are created, then the tasks are scheduled to run.

When they do run, the following is executed:

var t = ListToProcess[counter];

But at this stage count is already equal to 5.

Perhaps you should look at parallel collections?

ListToProcess.AsParallel().ForAll(list => dosomething(list));

There are many other options around this area.

Upvotes: 1

Related Questions