BackgroundWorker.ReportProgress reports wrong values
I have a class extracting strings from a large file (100,000) lines and wanted to give some progress feedback to the user. My problem is that the progress is not reported correctly.
I am using a custom class to send in the UserState property:
public enum UpdateType {FileCount, ProcessedFiles, LineCount, ProcessedLines, StepUpdate};
public class ProgressUpdate
{
public UpdateType UpdateType { get { } set { } }
public string Update { get { } set { } }
}
This is my method for handling the ReportProgress event:
public class BGWorkerBase : BackgroundWorker
{
public void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressUpdate update;
update = (ProgressUpdate)e.UserState;
switch (update.UpdateType)
{
case UpdateType.FileCount:
MainWindow.FrmDisplayProgress.FileCount = update.Update;
break;
case UpdateType.ProcessedFiles:
MainWindow.FrmDisplayProgress.FileProgress = update.Update;
break;
case UpdateTy开发者_StackOverflow中文版pe.LineCount:
MainWindow.FrmDisplayProgress.LineCount = update.Update;
break;
case UpdateType.ProcessedLines:
MainWindow.FrmDisplayProgress.LineProgress = update.Update;
MainWindow.FrmDisplayProgress.PrecentProgress = e.ProgressPercentage;
break;
case UpdateType.StepUpdate:
MainWindow.FrmDisplayProgress.AddLine(update.Update);
break;
}
}
}
MainWindow.FrmDisplayProgress is a call to a form that displays progress. And last, this is the worker class:
public class TraceFile
{
public HashSet<string> ExtractValues(HashSet<MyRegex> regexList, BackgroundWorker worker)
{
HashSet<string> results = new HashSet<string>();
int rowNumber = 1;
foreach (DataRow row in Lines.Rows)
{
int percentComplete = (int)(((float)rowNumber / (float)Lines.Rows.Count) * 100);
worker.ReportProgress(percentComplete, new ProgressUpdate(UpdateType.ProcessedLines, rowNumber++.ToString()));
// using multiple regex supports different formats for the same data type. For example - more then 1 account format
foreach (MyRegex regex in regexList)
{
MatchCollection matches = regex.Matches(row["Text"].ToString());
if (matches.Count > 0)
{
foreach (Match result in matches)
results.Add(result.ToString());
}
}
}
return results;
}
}
This is the case that catches these specific type of updates :
case UpdateType.ProcessedLines:
MainWindow.FrmDisplayProgress.LineProgress = update.Update;
MainWindow.FrmDisplayProgress.PrecentProgress = e.ProgressPercentage;
break;
'MainWindow.FrmDisplayProgress.PrecentProgress = e.ProgressPercentage;' is updating a progress bar. Instead of slowly moving from 0 to 100 once, the progress bar moves from 0 to 100 quickly several times. 'MainWindow.FrmDisplayProgress.LineProgress = update.Update' is updating a label with the line number but its not doing anything. For some reason, while in debug mode I saw both updating correctly so I'm suspecting some threading issues.
I hope I managed to present all this in a clear manner.
Solution for the issue:
- The progress bar running several times was a red herring and is not related to the issue. This happened becasue the method was entered several times.
The label not updating is due to too frequent calls (see the answer to this question below for more details). I changed the method to this:
public HashSet<string> ExtractValues(HashSet<MyRegex> regexList, BackgroundWorker worker) { HashSet<string> results = new HashSet<string>(); int rowNumber = 0; DateTime startCount = DateTime.Now; DateTime endCount = DateTime.Now; foreach (DataRow row in Lines.Rows) { rowNumber++; TimeSpan timeSpan = endCount.Subtract(startCount); if ((timeSpan.Milliseconds > 50) | (rowNumber == Lines.Rows.Count)) { int percentComplete = (int)(((float)rowNumber / (float)Lines.Rows.Count) * 100); worker.ReportProgress(percentComplete, new ProgressUpdate(UpdateType.ProcessedLines, rowNumber.ToString())); startCount = DateTime.Now; } // using multiple regex supports different formats for the same data type. For example - more then 1 account format foreach (MyRegex regex in regexList) { MatchCollection matches = regex.Matches(row["Text"].ToString()); if (matches.Count > 0) { foreach (Match result in matches) results.Add(result.ToString()); } } endCount = DateTime.Now; } return results; }
You are calling ReportProgress too often. Call it more frequently than about a thousand times per second and the UI thread gets so swamped by the invoke requests that it doesn't get around to its regular duties anymore. It stops painting and paying attention to user input. The updates still happen, you just can't see them anymore.
This of course happens because you have ReportProgress inside the inner loop, calling it for each individual row in the dataset. It is wasted effort, the human eye cannot possibly keep up with that rate of updates, anything beyond 20 updates per second looks like a blur. A Q&D fix would be to call ReportProgress for every 100th row. Paying attention to elapsed time and count off 45 msec is the fix that works on any machine.
The problem with the progress bar sounds like a calculation problem, one that's not obvious from the snippet. The reason the progress bar works but not the text update is that PB was designed to avoid this painting problem, it forces a repaint when its Value property changes instead of waiting for Windows to deliver the Paint notification.
Instead of slowly moving from 0 to 100 once, the progress bar moves from 0 to 100 quickly several times.
Did you set the Minimum
and Maximum
of the progress bar to 0 and 100?
The defaults for a WPF ProgressBar is 0 and 1.
In case you are use a WinForms progress bar the default is 0 and 100, and that shouldn't be the problem.
精彩评论