并行任务执行顺序

时间:2013-09-04 11:09:56

标签: 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(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)
{
    // ...
}

我需要记录发送到ProcessJob方法的任务的顺序,我使用index参数。但是这段代码:

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

不会给出执行操作的正确顺序。此代码将给出正确的顺序:

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

我不明白为什么Task的这个重载修复了这个问题。 i是否根据实际执行顺序传递给index参数?或者我的测试不正确,这段代码也会失败吗?

1 个答案:

答案 0 :(得分:4)

问题是你在循环变量i上创建闭包,并在循环开始后使用它的值。

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

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

index变量创建本地副本版本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();

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