C# 并行任务执行顺序

C# 并行任务执行顺序,c#,task-parallel-library,C#,Task Parallel Library,我有以下代码: 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(pJobsTo

我有以下代码:

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)
{
    // ...
}
不会给出正确的操作执行顺序。此代码将给出正确的顺序:

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

我不明白为什么这个任务的超负荷解决了这个问题。i是否根据实际执行顺序传递给index参数?或者我的测试不正确,并且该代码也会失败吗?

问题在于,您正在对循环变量
i
创建闭包,并在循环进行之后使用其值

使用
()=>ProcessJob(jobs[i],i)
创建lambda函数时,编译器会创建对变量
i
的隐藏引用,该变量由
for
循环更新。当
ProcessJob
作为正在执行的任务的一部分被调用时,将读取相同的引用

您看到的行为是竞争条件的结果:即使您在循环中启动任务,这些任务仍在单独的线程上运行,并且线程不会立即开始执行。当它们启动时,
i
的值已被修改(因为循环的更多迭代已完成),并且
ProcessJob
读取的值为“错误”

带有
索引
变量的版本创建了
i
的本地副本,该副本在逻辑上与之分离,并且不会与
i
一起修改;因此,在任务有机会开始之前,此副本不会被更改。您可以通过以下方式获得相同(正确)的行为:

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();

如上所述,创建捕获变量的本地副本是解决所有此类问题的方法。

问题在于,在循环变量
i
上创建闭包,并在循环进行之后使用其值

使用
()=>ProcessJob(jobs[i],i)
创建lambda函数时,编译器会创建对变量
i
的隐藏引用,该变量由
for
循环更新。当
ProcessJob
作为正在执行的任务的一部分被调用时,将读取相同的引用

您看到的行为是竞争条件的结果:即使您在循环中启动任务,这些任务仍在单独的线程上运行,并且线程不会立即开始执行。当它们启动时,
i
的值已被修改(因为循环的更多迭代已完成),并且
ProcessJob
读取的值为“错误”

带有
索引
变量的版本创建了
i
的本地副本,该副本在逻辑上与之分离,并且不会与
i
一起修改;因此,在任务有机会开始之前,此副本不会被更改。您可以通过以下方式获得相同(正确)的行为:

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();
如上所述创建捕获变量的本地副本是解决所有此类问题的方法