Trying to update a UI element that updates as DoWork() iterates through every line in a DataTable with a visual progress bar and a textbox that gives the current value. The iteration happens in a background worker and works as expected, as does the progress bar's PerformStep(). However, the TextBox's Text does not change.
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
table = SegmentationLibrary.Core.Utils.GetTableFromQuery(query);
backgroundWorker1.ReportProgress(table.Rows.Count, "Max");
int status = 0;
backgroundWorker1.ReportProgress(status);
table.Columns.Add("seg_rates_id", typeof(Int32));
foreach (DataRow row in table.Rows)
{
row["seg_rates_id"] = 0;
status++;
backgroundWorker1.ReportProgress(status);
Application.DoEvents();
}
}
Progress:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState != null && e.UserState.ToString() == "Max") {
progressBar.progressBar1.Maximum = e.ProgressPercentage;
}
else if (e.ProgressPercentage == 0)
{
progressBar.progressBar1.Minimum = 0;
progressBar.progressBar1.Style = ProgressBarStyle.Continuous;
progressBar.progressBar1.Value = 1;
progressBar.progressBar1.Step = 1;
progressBar.tbProgress.Text = "Setting initial rate types";
}
else
{
progressBar.progressBar1.PerformStep();
progressBar.tbProgress.Text = "Current at row " + progressBar.progressBar1.Value + " of " + progressBar.progressBar1.Maximum + ", " + progressBar.GetProgressBarPercent().ToString() + "%.";
}
}
Caller:
public void GetFinacData(int year, int month)
{
if (!backgroundWorker1.IsBusy)
{
Application.EnableVisualStyles();
currentMonth = month;
currentYear = year;
query = "SELECT * FROM FINAC_ACTUAL_DATA WHERE fiscal_year = " +
currentYear + " AND fiscal_month = " + currentMonth + ";";
progressBar.Show();
progressBar.progressBarStyle = ProgressBarStyle.Marquee;
progressBar.tbProgress.Text = "Downloading PSGL Extract from database";
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
// required for ProgressBar to update?
while (backgroundWorker1.IsBusy)
Application.DoEvents();
workerCompleted.WaitOne();
progressBar.Hide();
}
else
{
MessageBox.Show("Segmentation already in progress.","Error",MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
What's happening:
I tried a few different things, but none of them work. Adding another DoEvents() at the end of ProgressChanged causes a stack overflow. Any ideas?
you could wrap you ui changing code inside this code
this.Invoke(new Action(() => { /* ui changing code */ }));
You can do the updating from the background work, not from your main thread. At this point your main thread is still working and your updates are not processed.
remove
// required for ProgressBar to update?
while (backgroundWorker1.IsBusy)
Application.DoEvents();
workerCompleted.WaitOne();
progressBar.Hide();
then move the:
progressBar.Hide();
to the end of your background worker. And remove all DoEvents from your code. It is not needed to do the updates.
Related
I have two timers (System.Windows.Forms.Timer) in a WinForms application.
Both timers start at the same time. One timer starts and stops throughout the life of the program updating three labels, the other just runs and does its work at every tick event updating one label. However, when the first timer is running code in its tick event the second timer isn't running.
In the first timer tick event code I have inserted multiple System.Threading.Thread.Yield(); statements, but the second timer is still being blocked. Searches for this comes up null.
I tried using system threading for the second timer, but it isn't doing anything.
I'm at a loss.
Any ideas?
public partial class fMain2 : Form
{
private System.Windows.Forms.Timer timer;
private System.Windows.Forms.Timer timer2;
private Thread tThread;
private int totTime;
private int curTime;
private int exTime = 0;
public int runTime = 0;
private void cmdRun_Click(object sender, EventArgs e)
{
//calculate total time
totTime = iCount * iDuration;
lblTotTime.Text = totTime.ToString();
lblTotEx.Text = exTime.ToString();
System.Threading.Thread.Yield();
curTime = int.Parse("0" + txtDuration.Text);
System.Threading.Thread.Yield();
this.Refresh();
strFile = "Begin" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
//select first item in the listview
lvTimer.Items[0].Selected = true;
lvi = lvTimer.Items[0];
lvTimer.Refresh();
strFile = lvi.SubItems[1].Text + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
strFile = "Go" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
//attempted using a thread for timer2
timer.Start();
//tThread = new Thread(new ThreadStart(timer2.Start));
timer2.Start();
//tThread.Start();
}
private void timerTick(object sender, EventArgs e)
{
string strFile;
curTime -= 1;
totTime -= 1;
exTime += 1;
System.Threading.Thread.Yield();
lblCurTime.Text = curTime.ToString();
lblTotTime.Text = totTime.ToString();
lblTotEx.Text = exTime.ToString();
this.Refresh();
System.Threading.Thread.Yield();
if (curTime == 0)
{
timer.Stop();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
strFile = "Stop" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
if (totTime == 0)
{
//this marks the end of the program
timer2.Stop();
//tThread.Abort();
//more code but not relevant
return;
}
else
{ //we are still working down the listview
try
{
lvi = lvTimer.Items[lvi.Index + 1];
lvTimer.Items[lvi.Index].Selected = true;
lvTimer.FocusedItem = lvTimer.Items[lvi.Index];
lvTimer.Refresh();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
}
catch (IndexOutOfRangeException ei)
{
strFile = "End" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
bRunning = false;
ResetTime();
return;
}
curTime = int.Parse("0" + txtDuration.Text);
lblCurTime.Text = curTime.ToString();
lblTotTime.Text = totTime.ToString();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
//I'm wondering if the soundplayer is causing the problem
strFile = lvi.SubItems[1].Text + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
strFile = "Go" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
timer.Start();
}
}
}
private void timer2Tick(object sender, EventArgs e)
{
//this is all timer2 does. It runs as long as the
// program is running.
runTime += 1;
lblTotTotal.Text = (runTime / 60).ToString()
+ ":" + (runTime % 60).ToString("00");
}
}
I am using VS 2017.
System.Windows.Forms.Timer is designed to run code on the UI thread. Calling System.Threading.Thread.Yield() tells the system to run another thread that is ready to run on the current core, but your timers want to run on the UI thread so it doesn't help in any way.
It seems to me that you're blocking the UI thread playing your sound with .PlaySync().
I'm going to suggest that you push the playing of the sound to a background thread to free up the UI.
From what I can gather from your code, you're trying to play a series of sounds like this:
"Begin.wav", then "ListItem1.wav", then"Go.wav"
(wait a period of time)
"ListItem2.wav", then "Go.wav"
(wait a period of time)
"ListItem3.wav", then "Go.wav"
(wait a period of time)
"End.wav"
However, if a timer runs out then cancel playing these sounds and play a "Stop.wav" sound instead.
This structure can be modelled with a Queue<Queue<string>> and you just need to a nested dequeue and pay all of the sounds.
Queue<Queue<string>> queue =
new Queue<Queue<string>>(new[]
{
new Queue<string>(new[] { "Begin.wav", "ListItem1.wav", "Go.wav" }),
new Queue<string>(new[] { "ListItem2.wav", "Go.wav" }),
new Queue<string>(new[] { "ListItem3.wav", "Go.wav" }),
new Queue<string>(new[] { "End.wav" }),
});
Here's the code to dequeue the queues:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task task = Task.Run(async () =>
{
while (queue.Any())
{
if (ct.IsCancellationRequested)
{
break;
}
Queue<string> inner = queue.Dequeue();
while (inner.Any())
{
if (ct.IsCancellationRequested)
{
break;
}
string soundLocation = inner.Dequeue();
using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
{
sp.SoundLocation = soundLocation;
sp.PlaySync();
}
}
if (ct.IsCancellationRequested)
{
using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
{
sp.SoundLocation = soundLocationStop;
sp.PlaySync();
}
}
else
{
await Task.Delay(TimeSpan.FromSeconds(2.0));
}
}
});
Note that this is all run in a Task.Run so it's not blocking the UI thread.
To stop the processing of the sound, just call cts.Cancel();.
You now just need to build your queue in the cmdRun_Click method.
private void cmdRun_Click(object sender, EventArgs e)
{
string[][] soundLocationGroups =
(
from x in lvTimer.Items.Cast<ListViewItem>()
from y in x.SubItems.Cast<ListViewItem.ListViewSubItem>()
select new[] { Path.Combine(strSoundFilePath, $"{y.Text}.wav"), Path.Combine(strSoundFilePath, $"Go.wav") }
).ToArray();
soundLocationGroups =
soundLocationGroups
.Take(1)
.Select(xs => xs.Prepend(Path.Combine(strSoundFilePath, $"Begin.wav")).ToArray())
.Concat(soundLocationGroups.Skip(1))
.Append(new[] { Path.Combine(strSoundFilePath, $"End.wav") })
.ToArray();
string soundLocationStop = Path.Combine(strSoundFilePath, $"Stop.wav");
Queue<Queue<string>> queue = new Queue<Queue<string>>(soundLocationGroups.Select(x => new Queue<string>(x)));
You still need a single timer to know if you should call cts.Cancel().
I did test my Task.Run code before posting. It works.
My suggestion is to use async/await.
Step 1: Get rid of all the System.Threading.Thread.Yield(); calls.
Step 2: Get rid of all the .Refresh(); calls.
Step 3: Make the timerTick async: private async void timerTick(object sender, EventArgs e)
Step 4: Replace every occurrence of snd.PlaySync(); with await Task.Run(() => snd.PlaySync());
The Task.Run method invokes the specified lambda on the ThreadPool. The await allows the current thread to continue doing other things, like responding to user input, while the lambda is running on the ThreadPool. After the lambda completes, the current thread continues executing the code that follows the await, until the next await, or until the end of the handler.
After doing these changes the UI should be responsive again, and the other timer should be ticking normally every second.
Be aware that by making the timerTick handler async, it is now possible to be invoked in an overlapping manner. It's up to you to prevent the overlapping, by checking and updating fields of the form.
I have this issue with my background worker where it only fires after my task is completed and not during. I have made sure to segment the progress properly but its still not firing. I'm not sure what else I can do.
Here is my code:
private int segmentHalf = 0;
private int segmentFull = 0;
public AutoMaticOne()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var backgroundWorker = sender as BackgroundWorker;
List<PrintObject> pol = new List<PrintObject>();
var program = programsData.Get(programSelectionInput.Text);
var fields = fieldsData.GetAllByTaskId(program.Id);
pol = printerData.Load(Input, program.Name,fields,program.Delimiter);
pol = pol.OrderBy(x => x.FilePath).ToList();
if (pol != null)
{
for(int i =0;i<pol.Count();i++)
{
segmentHalf = (((i + 1) / pol.Count()) * 100) / 2;
segmentFull = segmentHalf * 2;
backgroundWorker.ReportProgress(segmentHalf);
print.Process(pol[i].FilePath, pol[i].PrinterDriver);
infoInput.Invoke((MethodInvoker)delegate {
infoInput.Text = infoInput.Text + "\r\n" + pol[i].FileName + " - " + pol[i].PrinterDriver;
});
backgroundWorker.ReportProgress(segmentFull);
}
}
}
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
ProgressBar.Value = e.ProgressPercentage;
ProgressBarLabel.Text = e.ProgressPercentage + "%";
}
private void backgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
InputDirectory.Enabled = true;
OutputDirectory.Enabled = true;
InputDirectoryButton.Enabled = true;
OutputDirectoryButton.Enabled = true;
MessageBox.Show("Task has been Completed, Output files can be found at " + OutputDirectory.Text, "Task Completed");
Start.Enabled = true;
}
When I start with 10 files, it seems fine. But when it loads something like 1000, then it only reports the progress at the end.
SegmentHalf and Full are declared above the constructor
As long as i < pol.Count - 1, (i + 1) / pol.Count will be 0. Integer division truncates towards 0. Your code will execute 0 * 100 for all except the last file.
You can fix it like this:
//segmentHalf = (((i + 1) / pol.Count()) * 100) / 2;
segmentHalf = (((i + 1) * 100) / pol.Count) / 2;
I'm using a thread to run a calculation in the background of my program. I start the thread at the start of my program. If I press a button before the thread is finished it will open the statusBar and "openedStatus" is set to true.
This will show the threads current progress and after the thread has finished I would like to execute the last part of my code:
if (openedStatus)
{
sb.Close();
validateBeforeSave();
}
This part of the code will throw an exception though because you can't close the statusbar cross-thread.
Now the question is: How can I execute that last part of the code after the thread is finished?
private StatusBar sb = new StatusBar();
private void startVoorraadCalculationThread()
{
sb.setMaxProgress(data.getProducten().getProductenCopy().Count);
Thread thread = new Thread(new ThreadStart(this.run));
thread.Start();
while (!thread.IsAlive) ;
}
private void run()
{
for (int i = 0; i < data.getProducten().getProductenCopy().Count; i++ )
{
sb.setProgress(i);
sb.setStatus("Calculating Voorraad: " + (i+1) + "/" + data.getProducten().getProductenCopy().Count);
data.getProducten().getProductenCopy()[i].getTotaalVoorraad(data.getMaten());
}
if (openedStatus)
{
sb.Close();
validateBeforeSave();
}
calculationFinished = true;
}
Using a backgroundWorker fixed my problem:
private void startVoorraadCalculationThread()
{
sb.setMaxProgress(data.getProducten().getProductenCopy().Count);
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < data.getProducten().getProductenCopy().Count; i++)
{
sb.setProgress(i);
sb.setStatus("Calculating Voorraad: " + (i + 1) + "/" + data.getProducten().getProductenCopy().Count);
data.getProducten().getProductenCopy()[i].getTotaalVoorraad(data.getMaten());
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (openedStatus)
{
sb.Close();
validateBeforeSave();
}
calculationFinished = true;
}
This is the code of the DoWork event the ProgressChanged event and the RunWorkerCompleted event. The problem is that the progressBar is getting to 100% much faster before the function process operation is end. So when the progressBar is up to 100% I'm getting error exception since the progressBar can't be 101%
I need somehow to make that the progressBar will get progress according to the function process / progress. I guess that something is wrong with my calculation in the DoWork event.
In the Form1 top I added:
Int i;
In the constructor I did:
i = 0;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
And this is the events of the Backgroundworker:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//int currentLength;
//int currentIndex;
//int lastIndex = 0;
string startTag = "T256=\"";
string endTag = "\"";
int startTagWidth = startTag.Length;
//int endTagWidth = endTag.Length;
int index = 0;
h = new StreamReader(#"d:\DeponiaSplit\testingdeponias_Translated.txt");
while ((line = h.ReadLine()) != null)
{
if (index > f.LastIndexOf(startTag))
{
break;
}
int startTagIndex = f.IndexOf(startTag, index);
int stringIndex = startTagIndex + startTagWidth;
index = stringIndex;
int endTagIndex = f.IndexOf(endTag, index);
int stringLength = endTagIndex - stringIndex;
if (stringLength != 0)
{
string test = f.Substring(stringIndex, stringLength);
f = f.Substring(0, stringIndex) + line + f.Substring(stringIndex + stringLength);
if (listBox1.InvokeRequired)
{
textBox1.Invoke(new MethodInvoker(delegate { textBox1.Text = line; }));
}
i = i + 1;
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 1));
}
}
h.Close();
StreamWriter w = new StreamWriter(#"D:\New folder (24)\000004aa.xml");
w.AutoFlush = true;
w.Write(f);
w.Close();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//this.progressBar1.Text = (e.ProgressPercentage.ToString() + "%");
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.progressBar1.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.progressBar1.Text = ("Error: " + e.Error.Message);
}
else
{
this.progressBar1.Text = "Done!";
}
}
Why doesn't it work correctly?
Working using FileStream
Now I wanted to add to the progressBar a label which will show % and the label will move according to the progressBar I don't want to disable the progressBar green color progress just to add % and show the numbers in percentages.
So in the ProgressChanged event I did :
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label3.Text = (e.ProgressPercentage.ToString() + "%");
}
But it doesn't work - the label13 doesn't move only the % changed to 1. I want to see something like 1%...2%...3%... and so on. How can I do it?
Why don't you use File.ReadAllLines, it returns an array of strings. You could report your progress as the percentage of the line you are processing to the total number of lines:
string[] lines = File.ReadAllLines(#"d:\DeponiaSplit\testingdeponias_Translated.txt");
// ...
worker.ReportProgress(i * 100 / lines.Length);
The "ReportProgress()" method is reporting "i" right after "i" has been incremented with the "i+1" statment. i is a variable that is incremented every time you have found a string that has matched your provided parameters. I think the problem here is that you are reporting an integer without context. Do you know how many lines total in the file contain a string that matches your parameters. I would suggest reporting (i/totalNumLinesMatchingParameters) * 100. This would report a number in terms of percentage. However, this does not include the action of writing the File. So you may want to scale the above differently if you intend to include the action of writing the File in your progress bar/progress bar label...
i am developing a project (WPF) and i have a Datagrid the load more than 5000 records from the database so i used a BackgroundWorker to advice the user the data is loading but it is so slow , i need to wait almost 2 minutes to load the data from the database,instead if i don't use the BackgroundWorker i need to wait just 3 second to load the data in the Datagrid.
Here i write down the code snippet that i use for the BackgroundWorker :
private void RunWorker()
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker senderWorker = sender as BackgroundWorker;
dc = new DataClasses1DataContext();
var query = from c in dc.Contact_DDBB_Xavis
select
new
{
c.ContactID,
c.Continent,
c.Country,
c.City,
c.PostalCode,
c.CompanyName,
c.UserCreated,
c.DateCreated,
c.UserModified,
c.DateModified
};
if (query.Count() > 0)
{
for (int i = 0; i < query.Count(); i++)
{
int progressInPercent = (int)(((decimal)(i + 1) / (decimal)query.Count()) * 100);
worker.ReportProgress(progressInPercent, i);
System.Threading.Thread.Sleep(10);
e.Result = query.ToList();
}
}
if (senderWorker.CancellationPending)
{
e.Cancel = true;
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
this.dataGrid.DataContext = e.Result;
backGround.Visibility = Visibility.Collapsed;
duracel.Visibility = Visibility.Collapsed;
txtBackWORK.Visibility = Visibility.Collapsed;
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
for (double i = 0.0; i < e.ProgressPercentage; i++)
{
duracel.pthFiller.Width = 0;
double max = 312;
max = (double)e.ProgressPercentage;
duracel.pthFiller.Width = e.ProgressPercentage * 3.12;
duracel.txtStatus.Text = e.ProgressPercentage + " %";
txtBackWORK.Text = String.Format("Loading " + e.ProgressPercentage + " %");
}
}
now i don't know if there is something wrong in my code so i ask you some advice to how load faster the data from database without wait so long time.
Thanks for your attention.
Have a good time.
Cheers
Each time you call query.Count(), you're running another SQL query.
You should call Count() once, and store it in a local variable.
Also, you should only call `ReportProgress if the progress actually changed. (There's no point in calling it 1,000 times)
Umm... you are calling System.Threading.Thread.Sleep() in your code, in a loop, which appears to be calling the query multiple times.
Your update code also seems to be needlessly looping from 0 to the current percentage... not sure why?
Get rid of the whole progress indicator block as it exists. You are iterating over every single record, pausing your thread, re-calling the query and assigning it to e.Result and generally incurring overhead 5,000 times after your data has already loaded. This is useless.
If the data loads in a matter of seconds, show a marquee progressbar on the UI thread and everyone will be happy.