Iniciar tarefas no loop foreach usa o valor do último item

Eu estou fazendo uma primeira tentativa de jogar com as novas Tarefas, mas algo está acontecendo que eu não entendo.

Primeiro, o código, que é bastante direto. Eu passo uma lista de caminhos para alguns arquivos de imagem e tento adicionar uma tarefa para processar cada um deles:

public Boolean AddPictures(IList paths) { Boolean result = (paths.Count > 0); List tasks = new List(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; } 

Descobri que, se eu apenas deixasse isso funcionar com, digamos, uma lista de três caminhos em um teste de unidade, todas as três tarefas usariam o último caminho na lista fornecida. Se eu percorrer (e retardar o processamento do loop), cada caminho do loop será usado.

Alguém pode, por favor, explicar o que está acontecendo e por quê? Soluções alternativas possíveis?

Você está fechando a variável de loop. Não faça isso. Tome uma cópia em vez disso:

 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); } 

Seu código atual está capturando path – não o valor dele quando você cria a tarefa, mas a própria variável. Essa variável altera o valor toda vez que você passa pelo loop – assim, ele pode mudar facilmente quando o delegado é chamado.

Pegando uma cópia da variável, você está introduzindo uma nova variável toda vez que passa pelo loop – quando você captura essa variável, ela não será alterada na próxima iteração do loop.

Eric Lippert tem um par de posts que abordam isso com mais detalhes: parte 1 ; parte 2 .

Não se sinta mal – isso pega quase todo mundo 🙁

O lambda que você está passando para StartNew está referenciando a variável de path , que muda em cada iteração (ou seja, seu lambda está fazendo uso da referência de path , em vez de apenas seu valor). Você pode criar uma cópia local para que não esteja apontando para uma versão que será alterada:

 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); }