Watch for Foreach Parallel Streams

I am currently writing a simple WPF file copy application that copies files in parallel. So far, this works great! He does everything that I want him to do. The operation meat is in the following code block:

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

I can implement ParallelOptions and limit the maximum number of threads to 4, but I was wondering if there is a way to accurately track what each thread will do in this case?

For example, let's say I have a part of my user interface that focuses on the current β€œstatus” of a copy operation. I would like to have 4 rows in the Grid , each of which had a specific stream and which file it was currently copying.

I know that I can use Interlocked to manage variables that are outside of the Parallel loop, but how would I keep track of thread-specific variables from within the Parallel loop and use these variables to save the UI on which the stream in which the file is running?

+5
source share
2 answers

Instead of tracking threads directly, the user interface is associated with an ObserveableCollection<ProgressDetail> , each of which represents progress, then in your loop it adds an element to the collection when it starts, then removes it from the collection when it ends.

One thing you must be careful about is thread safety, ObseveableCollection not thread safe, so you should only interact with it in thread safe ways, the easiest way to do this is to make all the addition and removal of ProgressDetail objects in the user interface thread. This also has the added benefit of capturing the SynchronizationContext of a user interface stream when creating a Progress object.

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

See this answer for an XCopy example that will be copied with progress. I made the assumption that the Copy signature was changed to

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

but I leave this actual change as an exercise for the reader.

UPDATE: I updated the above code example to open the public ProgressPercentage property, which can be bound and trigger the correct events. I also translated listening for the Progress event into the internal elements of the ProgressDetail class.

+3
source

The point about the Parallel library is that you do not know about threads, which is quite applicable to this example. Your loop makes some IO file and then computes. Although one of the threads performs IO, the thread is not used and can be reused for the calculation associated with one of the other files. That is why it is better to leave the number of threads or simultaneous tasks at run time: it knows better how much you can use.

Also as it is written _destDetail.Progress++; should really use Interlocked.Increment ! (And the .CurrOp challenge is also open to race conditions.)

+1
source

Source: https://habr.com/ru/post/1231964/


All Articles