Reputation: 311
I am looking for feedback in case there are any known reasons I shouldn't do something like this: putting a ReportProgress within a Parallel foreach statement. Are there any similar ways to achieve the same thing but are considered safer or better?
Code example:
int iterations = 0;
int count = items.Count;
Parallel.ForEach(items, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, item =>
{
iterations++;
int percentComplete = (iterations/count) * 100;
backgroundWorker.ReportProgress(percentComplete);
};
Additional info:
I have a Windows Form program that needs to do a specific task of managing items. Utilizing multi threading has made it much faster. However it needs to report to a progress bar on the form the overall progress completed so the end user doesn't wonder if the program frozen up. The overall job can take hours in some cases. So, having a progress bar is mandatory.
Upvotes: 0
Views: 2127
Reputation: 6344
I think Harald is on the money with his answer. I've used the example shown at this link in the instance of having one background worker.
You get a good feel for how it works with the following snippet (copied from the link above). I think you could maybe use that as a base to allow a collection of DoWorkEventHandler
delegates and create a BackgroundWorker
for each.
private void button4_Click(object sender, EventArgs e)
{
// Create dialog.
ProgressWithCancel dlgProgress = new ProgressWithCancel( "Testing progress", LongOperation);
// Show dialog with Synchronous/blocking call.
// LongOperation() is called by dialog.
dlgProgress.ShowDialog(); // Synchronous/blocking call.
}
private void LongOperation(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int max = 20;
for (int i = 0; i < max; i++)
{
if (worker.CancellationPending) // See if cacel button was pressed.
{
System.Threading.Thread.Sleep(2000); // Similate time for clean-up.
break;
}
int percent = i * 100 / max;
string userState = percent.ToString(); // render a string to display on the progress dialog.
// Append to string just to show multi-line user-status info.
if (percent >= 45 && percent <= 55) { userState += "Half way"; }
if (percent >= 85) { userState += "Almost done"; }
worker.ReportProgress(percent, userState); // Report percent and user-status info to dialog.
System.Threading.Thread.Sleep(800); // Simulate time-consuming operation
}
}
// Implementation (in ProgressWithCancel.cs)
namespace ProgressWithCancel
{
public partial class ProgressWithCancel : Form
{
public ProgressWithCancel(string whyWeAreWaiting, DoWorkEventHandler work)
{
InitializeComponent();
this.Text = whyWeAreWaiting; // Show in title bar
backgroundWorker1.DoWork += work; // Event handler to be called in context of new thread.
}
private void btnCancel_Click(object sender, EventArgs e)
{
label1.Text = "Cancel pending";
backgroundWorker1.CancelAsync(); // Tell worker to abort.
btnCancel.Enabled = false;
}
private void Progress_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = e.UserState as string;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Close();
}
}
}
Upvotes: 1
Reputation: 30454
I think it is not wise, and possibly not even allowed to let several threads communicate with the same BackGroundWorker
.
If you have only a limited amount of threads, like 5 in your example, consider having each thread use its own BackgroundWorker that reports its own progress.
The receiver of all progress reports should upon receipt of a report decide about the actual progress and report it to those who are interested.
class MyLongProgress : IDisposable
{
public MyLongProgress()
{
for (int i=0; i<maxNrOfParallelProcesses; ++i)
{
var createdBackGroundWorker = this.CreateBackGroundWorker();
createdBackGroundWorker.ReportProgress += this.OnProgressReport;
backGroundWorkers.Add(createdBackGroundWorker);
}
}
// TODO: implement Disposable pattern that Disposes the background workers
private readonly List<BackGroundWorker> backGroundWorkers = new List<BackGroundWorker>();
public void StartLongProcess()
{
// start your long progress, which involves starting the backGroundWorkers
// These backGroundWorkers will report progress via OnProgressReport
}
private void OnProgressReport(object sender, ...)
{
var myBackGroundWorker = (BackGroundWorker)sender;
// calculate the actual progress using earlier received progress reports
MyProgressReport report = ...
OnReportProgress(report);
}
public event EventHandler<MyprogressReport> ReportProgrogress;
protected virtual void OnReportProgress(MyProgressReport report)
{
this.ReportProgress?.Invoke(this, report);
}
}
Alternatively: create and dispose the BackGroundWorkers only during StartLongProcess. In that case you don't need IDisposable.
Upvotes: 1