1

我有以下代码:

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 参数?还是我的测试不正确,这段代码也会失败?

4

1 回答 1

4

问题是您正在循环变量上创建闭包,并在循环i进行后稍后使用它的值。

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

您看到的行为是竞态条件的结果:即使您在循环启动任务,这些任务也会在单独的线程上运行,并且线程不会立即开始执行。当它们启动时, 的值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();

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

于 2013-09-04T11:15:43.747 回答