我正在尝试使用新任务,但有些事情正在发生,我不明白。
首先,代码非常简单。我传入一些图像文件的路径列表,并尝试添加一个任务来处理它们中的每一个:
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;
}
我发现,如果我让它运行,例如,单元测试中的3个路径列表,则所有三个任务都使用提供列表中的最后一个路径。如果我单步执行(并减慢循环的处理速度),则使用循环中的每个路径。
有人可以解释一下发生了什么,为什么?可能的解决方法?
答案 0 :(得分:81)
你正在关闭循环变量。不要那样做。改为复制:
foreach (string path in paths)
{
string pathCopy = path;
var task = Task.Factory.StartNew(() =>
{
Boolean taskResult = ProcessPicture(pathCopy);
return taskResult;
});
// See note at end of post
task.ContinueWith(t => result &= t.Result);
tasks.Add(task);
}
当前代码正在捕获path
- 而不是创建任务时的值,而是变量本身。每次进行循环时,该变量都会更改值 - 因此,在调用委托时,它可以轻松更改。
通过获取变量的副本,每次循环时都会引入一个 new 变量 - 当你捕获那个变量时,它不会在循环的下一次迭代中被改变。
Eric Lippert有一对博客文章更详细地介绍了这一点:part 1; part 2
不要感觉不好 - 这几乎每个人都抓住了:(
注意这一行:
task.ContinueWith(t => result &= t.Result);
正如评论中所指出的,这不是线程安全的。多个线程可以同时执行它,可能会对彼此的结果加以冲突。我没有添加锁定或任何类似的东西,因为它会分散该问题感兴趣的主要问题,即变量捕获。但是,值得注意的是。
答案 1 :(得分:12)
您传递给StartNew
的lambda引用了path
变量,该变量在每次迭代时都会发生变化(即您的lambda正在使用{em> reference of { {1}},而不仅仅是它的价值)。您可以创建它的本地副本,以便您不指向将要更改的版本:
path