我有以下代码:
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参数?还是我的测试不正确,此代码还会失败吗?
问题是您要在循环变量上创建闭包,并在循环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();
如上所述创建捕获的变量的本地副本是解决所有此类问题的解决方案。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句