2011-01-13 12 views
37

Podejmuję pierwszą próbę gry z nowymi zadaniami, ale coś się dzieje, czego nie rozumiem.Uruchamianie zadań w foreach Wykorzystanie pętli Wartość ostatniej pozycji

Najpierw kod, który jest dość prosty. Mijam na liście ścieżek dostępu do niektórych plików graficznych i próbować dodać zadanie do przetwarzania każdego z nich:

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

Odkryłam, że jeśli po prostu niech to bieg z, powiedzmy, Lista 3 ścieżki w teście jednostkowym, wszystkie trzy zadania wykorzystują ostatnią ścieżkę na podanej liście. Jeśli przejdę (i spowolnić przetwarzanie pętli), używana jest każda ścieżka z pętli.

Czy ktoś może wyjaśnić, co się dzieje i dlaczego? Możliwe rozwiązania?

+3

Mogę zaproponować użyciu ReSharper Ten szczególny błąd i inne potencjalne błędy są highlighten dla Ciebie –

Odpowiedz

73

Ty zamykania nad zmiennej pętli. Nie rób tego. Zrób kopię zamiast:

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

Twój bieżący kod jest przechwytywanie path - nie wartość z niego podczas tworzenia zadania, ale sam zmienną. Ta zmienna zmienia wartość za każdym razem, gdy przechodzisz przez pętlę - więc może łatwo zmienić się do czasu, kiedy zostanie wywołany Twój delegat.

Biorąc kopię zmiennej, jesteś wprowadzenie nowego zmienną za każdym razem iść przez pętlę - Podczas robienia że zmienną, nie zostanie zmieniony w następnej iteracji pętli .

Eric Lippert ma parę wpisów na blogu, które zawierają więcej szczegółów: part 1; part 2.

Nie czuję się źle. - to łapie prawie wszyscy się :(

+1

ale oczywiście Las dla drzew i to wszystko.. :) –

+1

ten problem z zamknięciem i niewłaściwe użycie Random() musi znajdować się w pierwszej piątce pod względem częstotliwości w SO – BrokenGlass

+0

Należy zauważyć, że ten "błąd" (który pierwotnie był * według projektu *) powinien zostać naprawiony w C# 5.0 –

12

Lambda, że ​​jesteś przechodząc do StartNew odwołuje zmienną path, która zmienia się w każdej iteracji (to Twój lambda jest wykorzystanie odniesienia z path, a nie tylko jego wartość). Można utworzyć lokalną kopię, dzięki czemu nie są skierowane do wersji, która zmieni:

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