我目前正在编写一个简单的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更新哪个线程正在处理哪个文件?
答案 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也适用于竞争条件。)