在foreach中启动任务循环使用最后一项的值

我正在尝试玩新的任务,但有些事情我不明白。

首先,代码非常简单。 我传递一些path到一些图像文件,并尝试添加一个任务来处理它们中的每一个:

public Boolean AddPictures(IList<string> paths) { Boolean result = (paths.Count > 0); List<Task> tasks = new List<Task>(paths.Count); foreach (string path in paths) { var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(path); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); } Task.WaitAll(tasks.ToArray()); return result; } 

我发现,如果我让这个运行,比如一个unit testing中的3个path列表,所有这三个任务都使用提供的列表中的最后一个path。 如果我一步一步(并减慢循环的处理),则使用循环中的每条path。

有人可以解释发生了什么,为什么? 可能的解决方法?

你正在closures循环variables。 不要这样做。 取一个副本:

 foreach (string path in paths) { string pathCopy = path; var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(pathCopy); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); } 

您当前的代码正在捕获path – 而不是创build任务时的 ,而是variables本身。 每次你通过循环时,这个variables都会改变值 – 所以在你的委托被调用的时候,它可以很容易地改变。

通过获取variables的副本,每次循环都会引入一个variables – 捕获variables时,在循环的下一次迭代中不会更改。

Eric Lippert有一对博客文章详细介绍了这一点: 第1部分 ; 第2部分 。

不要觉得不舒服 – 这几乎每个人都赶出:(

传递给StartNew的lambda引用了pathvariables,每次迭代都会改变(即,lambda正在使用path引用 ,而不仅仅是它的值)。 您可以创build它的本地副本,以便您不指向将会更改的版本:

 foreach (string path in paths) { var lambdaPath = path; var task = Task.Factory.StartNew(() => { Boolean taskResult = ProcessPicture(lambdaPath); return taskResult; }); task.ContinueWith(t => result &= t.Result); tasks.Add(task); }