I'm writing a simple application (for testing purposes) that adds 10 M elements to a ListBox. I'm using a BackgroundWorker to do the work and a ProgressBar control to display the progress.
Each element is a just a "Hello World!" string with and index that I'm adding during the process. My program takes ~ 7-8 seconds to fill the ListBox, and I thought if it's possible to speed up this, by using all the available cores on my PC (8).
To achieve that, I've tried to use the TPL library, more precise the Parallel.For loop, but the results are unpredictable or it doesn't work as I want it to.
Here's the code of my application:
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();
}
}
And the parallel implementation that I've tried to write (this goes in 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;
}
});
The results is that the application freezes, and takes about 15 seconds to fill up my ListBox. (The elements are also unordered)
What can be done in this case and will parallelism speed up the "filling" process?
The lock statement in your thread basically reduces your parallel processing to sequential processing, but with the overhead of acquiring a lock (making it effectively slower).
Also there are a limited number of thread pool threads which can be used here so you won't get your full 10m concurrently adding.
I think a better way is to use a non UI thread to populate the list and then bind it afterwards - this will ensure the UI isn't frozen/unusable while the 10 million iteration loop is running:
Then you can call the UI thread when needed:
In an MVVM world you can just set the bound IEnumerable instead of the ItemsSource as shown in the above example.