【问题标题】:Parallel task execution order并行任务执行顺序
【发布时间】:2013-09-04 11:09:55
【问题描述】:

我有以下代码:

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

【问题讨论】:

    标签: c# task-parallel-library


    【解决方案1】:

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

    当使用 () =&gt; 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();
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-16
      • 1970-01-01
      • 2021-09-01
      • 2019-12-06
      相关资源
      最近更新 更多