我正在编写一个简单的应用程序(用于测试目的),它将10 M个元素添加到 ListBox 。我使用 BackgroundWorker 进行工作,使用 ProgressBar 控件来显示进度。
每个元素都只是一个" Hello World!"我在进程中添加的字符串和索引。我的程序需要大约7-8秒来填充 ListBox ,我想如果可以通过使用我PC上的所有可用内核(8)来加快这个速度。
为了达到这个目的,我尝试使用TPL库,更精确的是 Parallel.For 循环,但结果是不可预测的,或者它并不像我想要的那样工作它来。
这是我的申请代码:
private BackgroundWorker worker = new BackgroundWorker();
private Stopwatch sw = new Stopwatch();
private List<String> numbersList = new List<String>();
public MainWindow()
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
sw.Start();
int max = 10000000;
int oldProgress = 0;
for (int i = 1; i <= max; i++)
{
numbersList.Add("Hello World! [" + i + "]");
int progressPercentage = Convert.ToInt32((double)i / max * 100);
// Only report progress when it changes
if (progressPercentage != oldProgress)
{
worker.ReportProgress(progressPercentage);
oldProgress = progressPercentage;
}
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pb.Value = e.ProgressPercentage;
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lstLoremIpsum.ItemsSource = numbersList;
lblCompleted.Content = "OK";
lblCompleted.Content += " (" + numbersList.Count + " elements added" + ")";
lblElementiLista.Content += " (" +sw.Elapsed.TotalSeconds + ")";
worker.Dispose();
}
}
我尝试编写的并行实现(这在DoWork中):
Parallel.For(1, max, i =>
{
lock (lockObject)
{
numbersList.Add("Hello World! [" + i + "]");
}
int progressPercentage = Convert.ToInt32((double)i / max * 100);
// Only report progress when it changes
if (progressPercentage != oldProgress)
{
worker.ReportProgress(progressPercentage);
oldProgress = progressPercentage;
}
});
结果是应用程序冻结,并且需要 15秒来填充我的ListBox。 (元素也是无序的)
在这种情况下可以做些什么,而且并行性会加速&#34;填充&#34;过程
答案 0 :(得分:3)
线程中的lock语句基本上减少了对顺序处理的并行处理,但却带来了获取锁定的开销(使其有效地变慢)。
此外,可以使用有限数量的线程池线程,因此您不会同时添加完整的10米线程。
我认为更好的方法是使用非UI线程填充列表,然后将其绑定 - 这将确保在1000万次迭代循环运行时UI不会被冻结/不可用:
public MainWindow()
{
InitializeComponent();
Task.Factory.StartNew(PopList);
}
然后你可以在需要时调用UI线程:
private void PopList()
{
sw.Start();
int max = 10000000;
int oldProgress = 0;
for (int i = 1; i <= max; i++)
{
numbersList.Add("Hello World! [" + i + "]");
int progressPercentage = Convert.ToInt32((double)i / max * 100);
// Only report progress when it changes
if (progressPercentage != oldProgress)
{
Dispatcher.BeginInvoke(new Action(() => { pb.Value = progressPercentage; }));
oldProgress = progressPercentage;
}
}
Dispatcher.BeginInvoke(new Action(() => { lstLoremIpsum.ItemsSource = numbersList; }));
}
在MVVM世界中,您只需设置绑定的IEnumerable而不是ItemsSource,如上例所示。
答案 1 :(得分:0)
您正在锁定每个添加的列表,并且所有进程负载就是这样,向列表中添加一个元素,因此不会加快速度,因为实际上没有并行工作。
如果您的项目列表具有已知大小(看起来如此),则代替列表创建具有适当大小的数组,然后在并行for循环中将相应项目设置为它的值,这样就不会执行锁定,而且应该更快。
此外,在您的代码中,您不会显示填充列表视图的时间,只显示列表,因此我假设您使用此列表作为数据源,然后在设置它之前执行listView.BeginUpdate()并在设置之后它listView.EndUpdate(),它可能会加速一些事情,在添加元素时列表视图有点慢。
答案 2 :(得分:0)
如果您使用Parallel.For,那么您不需要BackgroundWorker。而工作人员无论如何都不会按预期工作,因为你正试图从另一个线程访问它。
删除BackgroundWorker并直接执行Parallel.For,使用Interlocked方法更新进度条:
private int ProgressPercentage { get; set; }
private void DoWork()
{
Parallel.For(1, max, i =>
{
lock (lockObject)
{
numbersList.Add("Hello World! [" + i + "]");
}
int progressPercentage = Convert.ToInt32((double)i / max * 100);
// Only report progress when it changes
if (progressPercentage != oldProgress)
{
Interlocked.Exchange(ProgressPercentage, progressPercentage);
ShowProgress();
}
});
}
private void ShowProgress()
{
pb.Value = ProgressPercentage;
}