C# 跟踪并行Foreach线程

C# 跟踪并行Foreach线程,c#,wpf,multithreading,parallel-processing,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.R

我目前正在编写一个简单的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++;
});
Parallel.ForEach(Directory.GetFiles(dir.ToList(),file=>
{
_destdail.CurrOp=string.Format(“复制文件:{0}”,Path.GetFileName(文件));
File.Copy(File,File.Replace(_destdail.Source,_destdail.Dest),true);
如果(_destDetail.Progress<_destDetail.MaxProgress)
_destDetail.Progress++;
});
我可以实现
ParallelOptions
并将最大线程数限制为4个,但我想知道是否有一种方法可以准确地跟踪在这种情况下每个线程将执行的操作

例如,假设我的UI中有一部分专用于复制操作的当前“状态”。我希望在
网格中有4行
,每行都有一个特定的线程,并且当前正在复制哪个文件


我知道我可以使用
联锁
来操作
并行
循环之外的变量,但是,我如何从
并行
循环内部跟踪特定于线程的变量,并使用这些变量使UI保持哪个线程在哪个文件上工作的最新状态?

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


也如所写的
\u destDetail.Progress++应该真正使用
联锁。增量
!(并且调用.CurrOp也会受到竞争条件的影响。)

不直接跟踪线程,而是让UI绑定到表示进度的
ObserveableCollection
,然后在循环中让它在集合开始时向集合添加一个项,然后在集合结束时将其从集合中删除

您必须注意的一点是线程安全,
obsevableCollection
不是线程安全的,因此您只能以线程安全的方式与它交互,最简单的方法是在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));
    }
}
但我把实际的变化留给读者作为练习


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

您提到线程安全,然后提到绑定。无论您在添加/删除集合时如何锁定集合,都不能真正使绑定线程安全。@Blindy我没有使用任何锁定,我做了所有可能导致UI线程触发绑定的修改,这就是我的代码通过使用
dispatcher所做的。调用
进行添加和删除以及报告进度(
progress
将在创建时使用
SynchronizationContext
,如果是UI线程,则会在UI线程上引发事件)。仅供参考,
File.Copy
Parallel.ForEach
内部执行是个坏主意,您应该只在CPU受限的工作中使用
Parallel.ForEach
,对于IO绑定的工作,其调度算法将尝试启动太多的任务,并且很可能需要比您刚刚使用单个线程在正常的
foreach
循环中复制文件更长的时间来完成。
public static void Copy(string source, string destination, bool overwrite, bool nobuffering, IProgress<double> handler)