简体   繁体   中英

Parallel task execution order

I have the following code:

public IEnumerable<Task> ExecuteJobs(Action[] pJobsToExecute)
{
    var tasks = new Task[pJobsToExecute.Length];
    for (int i = 0; i < pJobsToExecute.Length; i++)
    {
        //tasks[i] = new Task((index) => ProcessJob(pJobsToExecute[(int)index], (int)index), i);
        //tasks[i].Start();
        tasks[i] = new Task(() => ProcessJob(pJobsToExecute[i], i));
        tasks[i].Start();
    }

    return tasks;
}

public void ProcessJob(Action jobToProcess, int index)
{
    // ...
}

I need to log the order of the tasks being sent to the ProcessJob method, I use the index parameter for this. But this code:

tasks[i] = new Task(() => ProcessJob(jobs[i], i));
tasks[i].Start();

Will not give the correct order in which the actions will be executed. This code will give the correct order:

tasks[i] = new Task((index) => ProcessJob(pJobsToExecute[(int)index], (int)index), i);
tasks[i].Start();

I don't understand why this overload for Task fixes the issue. Does the i get passed to the index parameter based on actual order of execution? Or have my tests been incorrect and will this code also fail?

The problem is that you are creating closure over the loop variable i and using its value later on, after the loop has progressed.

When creating a lambda function with () => ProcessJob(jobs[i], i) the compiler creates a hidden reference to the variable i which is updated by the for loop. The same reference is read when ProcessJob is called as part of the task being executed.

The behavior you see is the result of a race condition: even though you are starting the tasks inside the loop, those tasks run on separate threads and the threads do not start executing immediately. When they do start the value of i has been modified (because more iterations of the loop have been completed) and the value read by ProcessJob is "wrong".

The version with the index variable creates a local copy of i which is logically separate from it and is not modified together with i ; this copy is therefore immune to being changed before the task has a chance to start. You would get the same (correct) behavior with:

var index = i;
// Note that tasks[index] can also be tasks[i] -- it makes no difference
// because that value is used strictly during the current loop iteration
tasks[index] = new Task(() => ProcessJob(pJobsToExecute[index], index));
tasks[index].Start();

Creating a local copy of captured variables as above is the solution to all problems of this type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM