2011-01-13 10 views
37

Sto facendo un primo tentativo di giocare con i nuovi compiti, ma qualcosa sta accadendo che non capisco.Attività iniziali nel ciclo foreach Utilizza il valore dell'ultimo elemento

In primo luogo, il codice, che è piuttosto semplice. Io passo in un elenco di percorsi di alcuni file di immagini, e si tenta di aggiungere un'attività per elaborare ciascuno di essi:

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

Ho scoperto che se io lascio questa corsa con, diciamo, un elenco di 3 percorsi in un test unitario, tutte e tre le attività utilizzano l'ultimo percorso nell'elenco fornito. Se passo attraverso (e rallenta l'elaborazione del ciclo), viene utilizzato ogni percorso dal ciclo.

Qualcuno può spiegare cosa sta succedendo e perché? Possibili soluzioni alternative?

+3

Posso suggerisco di usare ReSharper Questo particolare errore ed altri bug potenziali highlighten per voi –

risposta

73

Stai chiudendo sopra l'indice del ciclo. Non farlo. Prendere una copia invece:

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

Il codice attuale è catturare path - non il valore di esso quando si crea il compito, ma la variabile stessa. Quella variabile cambia valore ogni volta che si passa attraverso il ciclo, quindi può facilmente cambiare nel momento in cui viene chiamato il delegato.

Prendendo una copia della variabile, si sta introducendo una nuova variabile ogni volta che si passa attraverso il ciclo - quando si cattura che variabile, non sarà cambiato nella prossima iterazione del ciclo .

Eric Lippert ha un paio di post sul blog che contengono molti più dettagli: part 1; part 2.

Non sento male -. Questa cattura quasi tutti fuori :(

+1

Ma naturalmente foresta per gli alberi e tutto il resto.. :) –

+1

questo problema di chiusura e l'uso corretto di Random() deve essere tra i primi 5 in termini di frequenza in SO – BrokenGlass

+0

Si noti che questo "bug" (che era in origine * di design *) dovrebbe essere corretto in C# 5.0 –

12

Il lambda che si sta passando a StartNew fa riferimento la variabile path, che cambia ad ogni iterazione (vale a dire la vostra lambda sta facendo uso del di riferimento di path, piuttosto che solo il suo valore). È possibile creare una copia locale di esso in modo che non si punta a una versione che cambierà:

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