跟踪并行 Foreach 线程
Keep track of Parallel Foreach threads
我目前正在编写一个简单的 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 专用于当前的 "Status" 复制操作。我想在 Grid
中有 4 行,每行都有一个特定的线程以及它当前正在复制哪个文件。
我知道我可以使用 Interlocked
来操作 Parallel
循环之外的变量,但是我如何跟踪 内部 [=] 中的线程特定变量Parallel
循环的 25=] 并使用这些变量使 UI 在哪个线程正在处理哪个文件时保持最新?
关于 Parallel 库的要点是您不知道线程 - 这非常适用于此示例。您的循环执行一些文件 IO,然后执行一些计算。当其中一个线程正在执行 IO 时,该线程未被使用并且可以重新用于执行与其他文件之一相关联的计算。这也是为什么最好将线程数或并发任务数留给 运行 时间:它比你更清楚它可以使用多少。
也写成_destDetail.Progress++;
真的应该用Interlocked.Increment
! (并且调用 .CurrOp 也对竞争条件开放。)
不是直接跟踪线程,而是让 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));
}
}
参见 this answer 示例 XCopy
class 将随着进度进行复制。我假设 Copy
的签名已更改为
public static void Copy(string source, string destination, bool overwrite, bool nobuffering, IProgress<double> handler)
但我将实际更改留作 reader 的练习。
更新: 我已经更新了上面的代码示例以公开一个 public 属性 ProgressPercentage
可以绑定到并引发适当的事件。我还将 Progress
事件的监听移到了 ProgressDetail
class.
的内部
我目前正在编写一个简单的 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 专用于当前的 "Status" 复制操作。我想在 Grid
中有 4 行,每行都有一个特定的线程以及它当前正在复制哪个文件。
我知道我可以使用 Interlocked
来操作 Parallel
循环之外的变量,但是我如何跟踪 内部 [=] 中的线程特定变量Parallel
循环的 25=] 并使用这些变量使 UI 在哪个线程正在处理哪个文件时保持最新?
关于 Parallel 库的要点是您不知道线程 - 这非常适用于此示例。您的循环执行一些文件 IO,然后执行一些计算。当其中一个线程正在执行 IO 时,该线程未被使用并且可以重新用于执行与其他文件之一相关联的计算。这也是为什么最好将线程数或并发任务数留给 运行 时间:它比你更清楚它可以使用多少。
也写成_destDetail.Progress++;
真的应该用Interlocked.Increment
! (并且调用 .CurrOp 也对竞争条件开放。)
不是直接跟踪线程,而是让 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));
}
}
参见 this answer 示例 XCopy
class 将随着进度进行复制。我假设 Copy
的签名已更改为
public static void Copy(string source, string destination, bool overwrite, bool nobuffering, IProgress<double> handler)
但我将实际更改留作 reader 的练习。
更新: 我已经更新了上面的代码示例以公开一个 public 属性 ProgressPercentage
可以绑定到并引发适当的事件。我还将 Progress
事件的监听移到了 ProgressDetail
class.