paparazzo
paparazzo

Reputation: 45096

Task List not giving me what I expect

When I run the following I get the value of 100 for Task #{0} and cb

If I debug line by line in the loop then I get the correct answer.

How to fix this?

public static void TaskList()
{
    ConcurrentBag<int> cb = new ConcurrentBag<int>();
    List<Task> taskArray = new List<Task>();
    for (int i = 0; i < 100; i++)
    {
        taskArray.Add(Task.Factory.StartNew((Object obj) => {
            int j = 0 + i;
            cb.Add(j);
            Debug.WriteLine("Task #{0} created on {1}",
                                j, Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(10);
        },
                                                i));
    }
    Task.WaitAll(taskArray.ToArray());
    foreach(var v in cb)
    {
        Debug.WriteLine(v);
    }
    Debug.WriteLine("");
}

Upvotes: 0

Views: 53

Answers (2)

pushasha
pushasha

Reputation: 789

You're seeing 100 getting print out for almost every run because the i in the following line is equivalent to 100 by the time the tasks are getting run:

int j = 0 + i;

I believe you're attempting to alleviate this problem with your int j = 0 + i line, but since the i is what's getting changed, j will still always be equivalent to whatever i is when the task is run. If you assign the value of i to j outside of your task, then you won't see this issue:

int j = i;

taskArray.Add(Task.Factory.StartNew((Object obj) => {
    cb.Add(j);
    Console.WriteLine("Task #{0} created on {1}",
                        j, Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(10);
}, , i));

Not sure I did a great job of explaining that, but I hope it helps.

Upvotes: 1

Federico Dipuma
Federico Dipuma

Reputation: 18265

You are passing a modified closure (i) inside your tasks.

When the Task is effectively executed the value of i is not determinated (in your case, it is 100 because the task starts after your for loop finishes).

You must avoid to capture modified closures inside delegates which are executed lazily (your Task snippet is an example, but the same could happen using LINQ on IEnumerable).

Instead, assign your value to a local variable and pass it inside the Task action:

for (int i = 0; i < 100; i++)
{
    var count = i;
    taskArray.Add(Task.Factory.StartNew((Object obj) => {
        int j = 0 + count;
        cb.Add(j);
        Debug.WriteLine("Task #{0} created on {1}",
                            j, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(10);
    },
                                            i));
}

Upvotes: 3

Related Questions