跟踪Parallel Foreach线程

时间:2015-09-21 17:47:26

标签: c# wpf multithreading parallel-processing

我目前正在编写一个简单的WPF文件复制应用程序,它可以并行复制文件。到目前为止它很棒!它做我想做的一切。操作的内容在以下代码块中:

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

我可以实现ParallelOptions并将最大线程数限制为4,但我想知道是否有办法准确地跟踪每个线程在这种情况下会做什么?

例如,假设我的UI的一部分专用于当前的状态&#34;状态&#34;复制操作。我想在Grid中有4行,每行都有一个特定的线程,以及它当前正在复制的文件。

我知道我可以使用Interlocked来操作Parallel循环之外的变量,但是如何跟踪里面中的特定于线程的变量Parallel循环并使用这些变量来保持UI更新哪个线程正在处理哪个文件?

2 个答案:

答案 0 :(得分:3)

不是直接跟踪线程,而是将UI绑定到表示进度的ObserveableCollection<ProgressDetail>,然后在循环中让它在集合启动时将其添加到集合中,然后在集合结束时将其从集合中删除。

您必须注意的一点是线程安全,ObseveableCollection不是线程安全的,因此您必须仅以线程安全的方式与它进行交互,最简单的方法是进行所有的添加和删除UI线程上的ProgressDetail个对象。这还有一个额外的好处,就是在创建Progress对象时捕获UI线程的SynchronizationContext。

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

有关将随进度复制的示例XCopy类,请参阅this answer。我假设Copy的签名已更改为

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

但是我把这个实际的改变作为读者的练习。

更新:我已更新上面的代码示例,以公开可绑定的公共属性ProgressPercentage并引发适当的事件。我还将Progress事件的监听转移到了ProgressDetail类的内部。

答案 1 :(得分:1)

关于并行库的一点是你不了解线程 - 这非常适用于这个例子。你的循环执行一些文件IO然后进行一些计算。当其中一个线程正在执行IO时,该线程未被使用,可以重新用于执行与其他文件之一相关的计算。这也是为什么最好将线程数或并发任务留给运行时:它比你知道它能使用多少更好。

同样,_destDetail.Progress++;写的应该真正使用Interlocked.Increment! (并且调用.CurrOp也适用于竞争条件。)