跟踪并行Foreach线程

本文关键字:线程 Foreach 并行 跟踪 | 更新日期: 2023-09-27 18:13:43

我目前正在编写一个简单的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中有一部分专门用于复制操作的当前"状态"。我想在Grid中有4行,每一行都有一个特定的线程和它当前正在复制的文件。

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

跟踪并行Foreach线程

不是直接跟踪线程,让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类的答案,它将随着进度进行复制。我假设Copy的签名已经更改为

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

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

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

关于Parallel库的要点是您不了解线程—这非常适用于本示例。你的循环做一些文件IO,然后做一些计算。当其中一个线程正在执行IO时,该线程没有被使用,并且可以被重用来执行与其他文件之一相关的计算。这也是为什么最好将线程或并发任务的数量留给运行时:它比您更清楚它可以使用多少。

也写_destDetail.Progress++;应该真正使用Interlocked.Increment !(调用. currop也对竞争条件开放)