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 UpdateType.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.
Related
I made a SUDOKU solver. When is solve a number it should be written to the screen but it's happen only when the solver is done. Why only then the screen is refreshing when it is done?
ii is the number, jj is the row, ll is the column
private void MainForm_Load(object sender, EventArgs e)
{
...
Thread tr2 = new Thread(adatbszal);
tr2.Start();
}
private void adatbszal()
{
while (fut)
{
Thread.Sleep(10);
if (adat[jj, ll] != 0)
{
SetText(jj, ll, adat[jj, ll].ToString());
}
else
{
SetText(jj, ll, "");
}
}
}
private void SetText(int i, int j, string adat2)
{
if (adatB[i,j].InvokeRequired)
{
valami d = new valami(SetText);
Invoke(d, new object[] { i, j, adat2 });
}
else
{
adatB[i, j].Text = adat2;
}
}
...
Thread th = new Thread(solver);
th.Start();
full project: https://drive.google.com/file/d/1sZTA4Ledfwl3romBY2UTvUoU9MZfY35g/view?usp=sharing
I would suggest putting a breakpoint on Invoke(d, new object[] { i, j, adat2 }); to verify that it is being reached, and when it is, add a breakpoint to if (adatB[i,j].InvokeRequired) before stepping, then make sure that the same parameters are being received to verify that the Invoke is triggering SetText as expected. (Step Into would be simpler, but I'm not sure that would work on an Invoke Line.)
If all that is working, then check if the display updates then adatB[i, j].Text = adat2; executes in the debugger.
I think your code is updating the display like you want it to, but it's not working properly in other regards. In one of my tests, it is just setting the first cell to "2" over and over again, so you won't see any change. You need to verify that the code is doing the right work before you verify that the display is updating properly. I think you may see many problems from having 3 threads running simultaneously which can all update or read the same global values when other threads are assuming these values (like jj) are not changing between lines. I would suggest not sharing data between threads if you are not very experienced with multi-threaded programming. Perhaps you can re-implement this as a single-threaded program that calls an update function periodically instead.
When I clicked button starts for loop from i=0 and I want to see on the label value of i. However I only see last values of i.
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text = i.ToString();
System.Threading.Thread.Sleep(1000);
}
}
}
when I run the my code I only see on the label ; 2 .
I want to see such a like below;
When the started For loop, i = 0, I must see the 0 on the Label.Text. An then when the i = 1, I must see 1 on the label.Text. And when i = 2, I must see 2 on the Label.Text.
I added Thread.Sleep(1000) however, result didn't change.
Where I am make mistake?
Please help me,
if you help me , I will appreciate you.
Thanks,
You need to append the lbl to get all the values. Right now, it finishes the loop and give you the last value in your label
public partial class Form1 : Form
{
int i;
public Form1()
{
InitializeComponent();
}
private void btnclick_Click(object sender, EventArgs e)
{
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text + = i.ToString();
}
}
}
Your problem is that you're doing work on the UI thread while expecting the UI thread to update your form.
While you process your loop, the UI thread is actually executing this code. The UI thread is therefore unable to update the form with the intermediate values you are setting within the loop. Once your code completes, the UI thread is then free to update the form. That's why you see the last value only.
You can see this better if you updated your code to loop ten million times instead of 3. Your form will become unresponsive and will appear locked up. That's because Windows knows your UI thread is locked in an intensive process and is unable to update the UI.
The solution is to use a background thread to run your process and synchronize updates with the UI thread. You'll also have to slow your loop down to see the changes, as others have suggested.
To learn more about how the UI thread works, and how to synchronize background threads with it, read this article (it's about WPF, but it covers the general case).
Each loop you re-write the string. Instead of saying
lblForLoopExample.Text = i.ToString();
You need to add to the string on each iteration. I'd create a variable to hold the value, and so something like this:
string myString = string.Empty;
for(i=0;i < 3; i++)
{
myString += i.ToString() + ", ";
}
lblForLoopExample.Text = myString.substring(0, (myString.length - 1));
substring is just so you don't have that trailing comma. It's kind of dirty code, but it will work.
First of all, your code won't compile as you are missing the exact format of the ToString() method in i.ToString along with the ;
lblForLoopExample.Text = i.ToString();
^^^
As per your code, you should try this:
for ( i = 0; i < 3; i++)
{
lblForLoopExample.Text += i.ToString() + ", ";
}
My problem is that the statusbar will get to 100% if the installation of DownloadInternetFile.exe will take to long time.
If i have a good connection it will work perfect, but if i have a bad connection i get a error saying the statusbar value can not be more than 100%.
if (Install_DownloadInternetFile == "Yes")
{
if (File.Exists("DownloadInternetFile.exe"))
{
var DownloadInternetFile = Process.Start("DownloadInternetFile.exe", "/q /norestart");
while (!DownloadInternetFile.HasExited)
{
this.LoadBar.Value += 1;
await Task.Delay(TimeSpan.FromSeconds(2));
}
DownloadInternetFile.WaitForExit();
}
else
{
TopMost = false;
MessageBox.Show("DownloadInternetFile.exe not found", "Error");
}
}
else
{
MessageBox.Show("Notselect");
}
LoadBar.Value = 8;
// start DownloadInternetFile2
if (Install_DownloadInternetFile2 == "Yes")
{
if (File.Exists("DownloadInternetFile2.exe"))
{
var Dotnet45 = Process.Start("DownloadInternetFile2.exe", "/q");
while (!DownloadInternetFile2.HasExited)
{
this.LoadBar.Value += 1;
await Task.Delay(TimeSpan.FromSeconds(2));
}
DownloadInternetFile2.WaitForExit();
}
else
{
TopMost = false;
MessageBox.Show("DownloadInternetFile2.exe not found", "Error");
}
}
else
{
MessageBox.Show("Notselect");
}
LoadBar.Value = 10;
the code gones on with 10 more exe files installing.
I think iam missing somthing in this part
this.LoadBar.Value += 1;
But can not figure it out
By default the "Value" property of the "ProgressBar" class can be in the range of 0 to 100. Your code does not intrinsically know how much of the work has been done unless you calculate it. However, in your loop you make no calculation as to how much work has been done, but instead adjust the value of the progress bar based on time. So if you know your task is going to take the same amount of time, no matter what the conditions, this is an acceptable calculation. However if the time your actions takes can vary you'll need to find some other way to calculate the value of the progress bar that reflects the true progress of the action. For example, if your action is a download then your bar should reflect the bytes downloaded divided by the total bytes of the download.
I have made a WPF application which has a button to move some files attached
to the column cells from one column to another column. The moment when I press
the button it shows a nice animation and moves all files to the next column's cells.
But my real problem is once I give my function color_check(),
my application is getting stuck. I really don't know why. Is there
any help I can get out for this?
Code:
private void button3_Click(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem((x) =>
{
using (var splashForm = new Form4())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
move(); //file moving function
//color_check(); if i give this fn, my form stucks and comes to live after 10 - 20 sec
done = true;
MessageBox.Show("TEST FINISHED");
Show();
}
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
// Application.DoEvents();
string fName1 = System.IO.Path.GetFileNameWithoutExtension(row.Cells[3].Value.ToString());
string fName2 = System.IO.Path.GetFileNameWithoutExtension(row.Cells[4].Value.ToString());
if (!string.IsNullOrEmpty(fName1) && !string.IsNullOrEmpty(fName2))
{
var f1 = GetValue(fName1.ToCharArray()[fName1.Length - 2]) * 16 + GetValue(fName1.ToCharArray()[fName1.Length - 1]);
var f2 = GetValue(fName2.ToCharArray()[fName2.Length - 2]) * 16 + GetValue(fName2.ToCharArray()[fName2.Length - 1]);
//if (System.IO.Path.GetFileName(fName1) != System.IO.Path.GetFileName(fName2))
if (f1 > f2)
{
//MessageBox.Show(fName1);
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Yellow;
row.Cells[3].Style = style;
}
else if (f2 > f1)
{
//MessageBox.Show(fName1);
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Yellow;
row.Cells[4].Style = style;
}
if (f1 == f2)
{
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.Plum;
row.Cells[4].Style = style;
row.Cells[3].Style = style;
}
}
}
}
The issue is that your code which is invoked on button3_click() is blocking the UI thread. That's why it appears to freeze for a while - the code is executing, and once it's complete the UI thread becomes responsive again.
The way to resolve this is to perform your actions asynchronously on another thread. In .NET 4 and beyond, you can use Tasks and the async/await keywords to help you manage this. If you are working on a version older than .NET 4, then you'll want to take a look at BackgroundWorker or other threading options compatible with your version of .NET.
Note that if you want to modify the GUI in your async method, you may need to use a Dispatcher.Invoke() to do so safely.
Here are some links to help you understand some approaches available to you
C# Blog on Understanding a simple async program
MSDN reference for async/await
Related StackOverflow question on how to use BackgroundWorkers
Related StackOverflow question on how to access the UI thread directly
In a normal UI application, no 2 functions that work on the GUI will run at the same time. This would result in a lot of issues otherwise, which usually would cause the program to crash. For example there could be 2 functions running at the same time that each check if the same list has at least one element and then remove an element - because they run at the same time they first both check if the list has 1 element.
That's why GUI functions all run in the same thread, meaning they run only one at a time. While color_check runs, other functions don't run.
You can launch additional threads and do work on them that is executed in parallel, of you can speed up the color_check function, for example by breaking it up into parts which run at lower priority one at a time, using a dispatcher
Take this:
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
ProcessRow(row);
}
}
}
and change it to this:
public void color_check() //this is my problem making fn
{
dataGridView1.Refresh();
string strVal = ini.ReadValue("Action", "Doc-Controller");
bool authenticated = true;
if (authenticated == UserInCustomRole(strVal))
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, ()=>{Process(row);});
}
}
}
In this code, Dispatcher.BeginInvoke tells the UI thread that it should run Process(row), as soon as it finds the time. This might result in 200 Process(row) calls that are waiting to be executed. It's still all executed on the UI thread and only one thing at a time. If a mouse click happens after the first hundred have been completed, the GUI thread will first finish number onehunderd and one, and then handle the mouseclick, before picking up the remaining calls to process.
There's a disadvantage to this approach. By allowing other functions to be executed in between different calls to Process(row), you may get surprising results. Especially if these other processes also change the cell styles.
I'm Doing a project on FileTransfer in which i have a listview , i will get events from one of my class file for updating the percentage of the file sent so far,after receiving it i will place the percentage in my listview ,while doing that the listview got
a flickering effect how to avoid it.i used application.doevents() but it doesnt works. i have seen in torrents while updating the percent the list doesnt get flickered
how to achieve this .
void Sender_Progress(int CurrentValue, string Ip) // here im receiving Events
{
try
{
//if (CurrentValue == 1)
// UpdateTimer.Enabled = true;
//list_send.Items[CurrentValue].SubItems[4].Text = Ip.ToString();
//Application.DoEvents();
obj = new object[] {CurrentValue, Ip };
list_send.Invoke(new UpdateList(UpList), obj);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void UpList(int Val, string ind) // here im updating the listview
{
Application.DoEvents();
int index = 0;
index = Convert.ToInt32(ind);
index = index - 1;
list_send.Items[index].SubItems[4].Text = Val.ToString();
if (Val == 100)
{
list_send.Items[index].SubItems[2].Text = "Completed.";
//UpdateTimer.Enabled = false;
}
//Application.DoEvents();
}
Firstly, you don't need the DoEvents, since you are already correctly working on two threads. Remove that. After that, I expect the problem is simply doing too much too quickly. Is it possible to batch updates, and only send an update, say, every 20? 50? times? It isn't clear what the control is, but many have multiple-update modes; for example with ListView:
theList.BeginUpdate();
try {
// make multiple updates here...
} finally {
theList.EndUpdate();
}
I would then see about passing over a list of updates, say, every 20 times (unless each takes a considerable time) [note it must be a different list per Invoke, and you need to remember to send any remaining items at the end, too].
Use worker thread - it's available from the toolbox and has two events that are invoked in the main (UI) thread.
The Progress event can be used to signal the listbox that it need to refresh or that the task was completed.
i overcome the flickering effect succesfully,im getting events frequently ,i will get an integer everytime, i will store it in a variable and compare it with next variable received by the event if it matches i wont invoke the listview,otherwise i will invoke it.now the flickering goes away. thanks all.