2015-09-21 8 views
5

Obecnie piszę prostą aplikację do kopiowania plików WPF, która kopiuje pliki równolegle. Jak na razie działa świetnie! Robi wszystko, co chcę. Mięso na działanie jest w kolejnym bloku kodu:Śledź wątki równoległe Foreach

Parallel.ForEach(Directory.GetFiles(dir).ToList(), file => 
{ 
    _destDetail.CurrOp = string.Format("Copying file: {0}", Path.GetFileName(file)); 
    File.Copy(file, file.Replace(_destDetail.Source, _destDetail.Dest), true); 
    if (_destDetail.Progress < _destDetail.MaxProgress) 
     _destDetail.Progress++; 
}); 

można zaimplementować ParallelOptions i ograniczenia maksymalnej liczby nici 4, a także, ale nie było zastanawiać, czy jest to sposób, aby dokładnie śledzić co każdy wątek robiłby w tym przypadku?

Załóżmy na przykład, że mam część mojego interfejsu użytkownika poświęconą bieżącemu "statusowi" operacji kopiowania. Chciałbym mieć 4 wiersze w Grid, z których każdy miał określony wątek i który plik aktualnie kopiował.

wiem, że mogę używać Interlocked manipulować zmiennych, które są na zewnątrz pętli Parallel, ale jak będę śledzić zmienne gwintu specyficznych od wewnątrz pętli Parallel i korzystanie z tych zmiennych, aby utrzymać UI do datę, w którym wątek działa na który plik?

+0

Tylko FYI, 'File.Copy' jest złym pomysłem do zrobienia wewnątrz' Parallel.ForEach', powinieneś używać tylko 'Parallel.ForEach' na pracy związanej z CPU, dla pracy związanej IO jej algorytm szeregowania spróbuje zacznij zbyt wiele zadań i najprawdopodobniej potrwa to dłużej, niż gdybyś właśnie skopiował pliki w normalnej pętli foreach za pomocą pojedynczego wątku. –

Odpowiedz

3

Zamiast śledzić wątki bezpośrednio, interfejs użytkownika jest powiązany z ObserveableCollection<ProgressDetail>, z których każdy przedstawia postęp, następnie w pętli dodaj element do kolekcji po uruchomieniu następnie usuń go z kolekcji po jej zakończeniu.

Jedno trzeba uważać na to bezpieczeństwo nici, ObseveableCollection nie jest bezpieczeństwo wątków, więc trzeba z nim kontaktować tylko w wątku bezpiecznych sposobów, najprostszym sposobem, aby to zrobić, to wszystkie dodaje i usunięcia ProgressDetail przedmiotów na wątek interfejsu użytkownika. Ma to również dodatkową zaletę polegającą na przechwytywaniu obiektu SynchronizationContext wątku interfejsu użytkownika podczas tworzenia obiektu Progress.

public ObserveableCollection<ProgressDetail> ProgressCollection {get; private set;} 

public void CopyFiles(string dir) 
{ 

    var dispatcher = Application.Current.Dispatcher; 
    Parallel.ForEach(Directory.GetFiles(dir).ToList(), file => 
    { 
     ProgressDetail progressDetail = null; 
     dispatcher.Invoke(() => 
     { 
      // We make the `Progress` object on the UI thread so it can capture the 
      // SynchronizationContext during its construction. 
      progressDetail = new ProgressDetail(file); 
      ProgressCollection.Add(progressDetail); 
     } 

     XCopy.Copy(file, file.Replace(_destDetail.Source, _destDetail.Dest), 
        true, false, progressDetail.ProgressReporter); 

     dispatcher.Invoke(() => ProgressCollection.Remove(progressDetail); 
    }); 

} 

public sealed class ProgressDetail : INotifyPropertyChanged 
{ 
    private double _progressPercentage; 

    public ProgressDetail(string fileName) 
    { 
     FileName = fileName; 
     ProgressReporter = new Progress<double>(OnProgressReported); 
    } 

    public string FileName { get; private set; } 
    public IProgress<double> ProgressReporter { get; private set; } 
    public double ProgressPercentage 
    { 
     get { return _progressPercentage; } 
     private set 
     { 
      if (value.Equals(_progressPercentage)) return; 
      _progressPercentage = value; 
      OnPropertyChanged(); 
     } 
    } 

    private void OnProgressReported(double progress) 
    { 
     ProgressPercentage = progress; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     var temp = PropertyChanged; 
     if(temp != null) 
      temp(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

Zobacz this answer na przykład XCopy klasy, która będzie kopiować z postępem. Zrobiłem założenie, że podpis Copy została zmieniona na

public static void Copy(string source, string destination, bool overwrite, bool nobuffering, IProgress<double> handler) 

ale zostawiam to rzeczywistą zmianę jako ćwiczenie dla czytelnika.

AKTUALIZACJA: Zaktualizowałem powyższy przykład kodu, aby pokazać publiczną własność publiczną ProgressPercentage, która może być wiązana i wywołuje właściwe zdarzenia. Przesłałem także odsłuchanie zdarzenia Progress do elementów wewnętrznych klasy ProgressDetail.

+0

Wspomniałeś o bezpieczeństwie wątku, potem wspominasz o wiązaniu. Naprawdę nie można zagwarantować, że wątek wiążący będzie bezpieczny, niezależnie od tego, czy blokujesz kolekcję podczas dodawania/usuwania z niej. – Blindy

+0

@Blindy Nie użyłem żadnego blokowania, robię wszystkie modyfikacje, które powodują wiązanie ognia w wątku UI, co jest tym, co robi mój kod, używając 'dispatcher.Invoke' dla dodawania i usuwania oraz [Postępu ' ] (https://msdn.microsoft.com/en-us/library/hh193692 (v = vs.110) .aspx) do raportowania postępu ('Postęp ' spowoduje podniesienie jego wartości ['PostępyChanged'] (https) : //msdn.microsoft.com/en-us/library/hh137516 (v = vs.110) .aspx) używając 'SynchronizationContext' w czasie jego tworzenia, jeśli był to wątek UI, to podnosi zdarzenia wątek UI). –

1

Punkt dotyczący biblioteki równoległej polega na tym, że nie wiesz o wątkach - co całkiem pasuje do tego przykładu. Twoja pętla wykonuje niektóre operacje wejścia/wyjścia, a następnie obliczenia. Podczas gdy jeden z wątków wykonuje IO, wątek nie jest używany i można go ponownie wykorzystać do wykonania obliczeń związanych z jednym z pozostałych plików. Właśnie dlatego lepiej jest pozostawić liczbę wątków lub zadań współbieżnych w czasie wykonywania: wie lepiej niż Ty ile może użyć.

Również jako napisane _destDetail.Progress++; powinno naprawdę używać Interlocked.Increment! (Wywołanie .CurrOp jest także otwarte na warunki wyścigów.)