Attività iniziali Nel ciclo foreach utilizza il valore dell’ultimo elemento

Sto facendo un primo tentativo di giocare con i nuovi compiti, ma sta succedendo qualcosa che non capisco.

Innanzitutto, il codice, che è piuttosto diretto. Trasmetto un elenco di percorsi ad alcuni file di immagine e tento di aggiungere un’attività per elaborare ognuno di essi:

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

Ho scoperto che se lascio correre, 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?

Stai chiudendo la variabile del ciclo. Non farlo. Prendine 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 tuo codice attuale sta acquisendo il path , non il valore di esso quando crei l’attività, 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 introduce una nuova variabile ogni volta che si passa attraverso il ciclo: quando si acquisisce quella variabile, non verrà modificata nella successiva iterazione del ciclo.

Eric Lippert ha un paio di post sul blog che approfondiscono molto più dettagliatamente: parte 1 ; parte 2 .

Non sentirti male – questo cattura quasi tutti fuori 🙁

Il lambda che stai passando a StartNew fa riferimento alla variabile path , che cambia su ogni iterazione (cioè il tuo lambda usa il riferimento del path , piuttosto che il suo valore). Puoi creare una copia locale di esso in modo che tu non stia puntando 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); }