2011-01-13 19 views
37

Estoy haciendo un primer intento de jugar con las nuevas tareas, pero sucede algo que no entiendo.Iniciar tareas en foreach Utiliza el valor del último elemento

En primer lugar, el código, que es bastante directo. Paso en una lista de rutas de acceso a algunos archivos de imagen, e intento de añadir una tarea de procesar cada una de ellas:

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

He descubierto que si me acaba de dejar esta carrera con, por ejemplo, una lista de 3 rutas en una prueba de unidad, las tres tareas usan la última ruta en la lista proporcionada. Si paso (y disminuyo el procesamiento del ciclo), se usa cada ruta desde el ciclo.

¿Alguien puede explicar lo que está sucediendo y por qué? ¿Posibles soluciones?

+3

Puedo sugiere emplear ReSharper Este error en particular y otros errores potenciales se highlighten para usted –

Respuesta

73

Usted está cerrando sobre la variable de bucle. No hagas eso. Tome una copia en su lugar:

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

su código actual está capturando path - no el valor de ella cuando se crea la tarea, pero la propia variable. Esa variable cambia de valor cada vez que pasas por el ciclo, por lo que puede cambiar fácilmente para cuando se llame a tu delegado.

Al tomar una copia de la variable, se está introduciendo una nueva variable decada vez que vaya a través del lazo - cuando se captura que variable, no se cambiará en la siguiente iteración del bucle .

Eric Lippert tiene un par de publicaciones en el blog que detallan esto con más detalle: part 1; part 2.

no se sienta mal -. Esto llama a cabo casi todo el mundo :(

+1

Pero, por supuesto bosque por los árboles y todo eso.. :) –

+1

este problema de cierre y el uso incorrecto de Random() debe estar entre los 5 primeros con respecto a la frecuencia en SO – BrokenGlass

+0

Tenga en cuenta que este "error" (que originalmente * por diseño *) se supone que debe corregirse en C# 5.0 –

12

La lambda que estás pasando a StartNew hace referencia a la variable path, que cambia en cada iteración (es decir, su lambda está haciendo uso de la referencia de path, en lugar de su valor). Se puede crear una copia local del mismo modo que usted no está apuntando a una versión que va a cambiar:

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