This question already has answers here:
progress bar not progressing
(4 answers)
Closed 7 years ago.
I have a method getPlugins that takes quite a long time to run. Essentially it's parsing a very large log file. I know the log file goes from time 0 to time 24. I would like to update a ProgressBar based on the current time. Here's the structure of my code, but the bar only seems to be updated once my loop is finished... How can I fix this?
private void getPlugins(String filePath)
{
var w = new Window2();
w.Show();
w.progress.Value = 0;
List<String> pluginNames = new List<String>();
string strLine;
// Read the file and display it line by line.
System.IO.StreamReader file = new System.IO.StreamReader(filePath);
while ((strLine = file.ReadLine()) != null)
{
// Do stuff....
float time; // Here I have time as a float from 0 to 24
w.progress.Value = time;
}
file.Close();
w.progress.Value = 24;
w.Close();
}
Basically the idea is that you can not update UI elements directly from a non UI thread. WPF has a Dispatcher class that gives u access to UI elements and using this you can update the UI elements like below code
Application.Current.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
{
w.progress.Value = time;
}));
Related
I am in a bottleneck finishing a GUI in Windows Forms C#.
I am 100% new doing this and I think that I am mixing and messing around. Someone could help me please?
I am embedding an artificial vision application (developed in HALCON software and exported to C#). I resume this app in one class with one method having three outputs (couple of images and a string).
I put this class inside a while loop with other classes to iterate and interact with the outputs from the vision app till state statusFile is 1 to exit the loop.
Firstly I was using only the mean thread and my GUI got unresponsive when inside the while loop was getting into the vision.
Snippet of Start button:
public string pathFile { get; set; } // THIS DATA COMES FROM PREVIOUS WFORM
public DataTable dataLonas { get; set; }
public string namePro { get; set; }
public Thread Run_thread = null, run1 = null;
public static AutoResetEvent myResetEvent
= new AutoResetEvent(false); // initially set to false.
public VentanaIniciarProceso3()
{
InitializeComponent();
}
private void button_start_process_Click(object sender, EventArgs e)
{
string name_button = button_start_process.Text;
if (name_button == "Start")
{
boton_iniciar_proceso1.Text = "Pause"; // CHANGE THE LABEL
// instead having more buttons
run1 = new Thread(t => //HERE THE NEW THREAD
{
while (statusFile == 0) //
{
HObject ho_IMAGE_OPERARIOS = null;
HObject ho_ActualImageTrim = null;
HTuple hv_READ_OCR_STRING = new HTuple();
// HALCON CLASS
(hv_READ_OCR_STRING, ho_ActualImageTrim, ho_IMAGE_OPERARIOS) =
LONASapp.action(hv_AcqHandle, hv_AcqHandle_2, pathFile, namePro);
string codigo = hv_READ_OCR_STRING.ToString();
// EVAL CODE
int aux_aviso = EvalCodigoBeta.analizarAvisoBeta(codigo,
dataLonas, pathFile, namePro);
// EVAL FILE CLASSFICHERO.
// statusFichero para 1 o 0
// Variable que indique si fuerza operario
statusFile = EvalFichero.checkCarga(dataLonas, pathFile, namePro);
statusFile = ContinuarSalirProyecto.continuarSalir(statusFile);
// IF statusFile==1 It Will exit
}
})
{ IsBackground = true };
run1.Start(); // START IN BACKGROUND THE LOOP WITH THE CLASSES
}
else if (name_button == "Pause")
{
myResetEvent.WaitOne(); // SAME BUTTON WITH LABEL CHANGED TRYING
// TO PAUSE THE THREAD
boton_iniciar_proceso1.Text = "Resume";
}
else
{
myResetEvent.Set(); // SAME BUTTON WITH LABEL CHANGED
// TO "RESUME" TO UNPAUSE
boton_iniciar_proceso1.Text = "Pause";
}
}
After doing this change, the GUI gets responsive which is nice and the correct way I am sure is using different threads. But when clicking again to the button which has changed the label to "Pause", it does not pause the thread run1, it continues… and now the GUI gets paused/unresponsive when cause of myResetEvent.WaitOne();
Could I ask you for help please? I am confused also and do not know where to continue or what to change…
Thanks a lot in advance. I really want to close this thing after 5 days not coming with the good idea.
Forgive me if this is simple. I come from the Embedded RToS world and have learnt c# for just for the purpose of test equipment for production. I think I have solved all the obvious issues already, just the tasks are not acting as expected.
I have a test jig with 10 serial ports and other things attached and a very basic windows forms UI with 10 list box's that need to all run simultaneously. I have cut the code down to just what is needed for the demo of the problem (no serial ports etc)
I am unable to get 10 listbox's (or textboxs) to update from 10 tasks properly. I can easily do one at a time, even 5 works, but 10 and it does not work.
I was getting exception out of range as it somehow gets passed a 10, but I only pass 0-9 so I reduced the task count to 9 for now until I understand the fundamental issue(s).
private void buttonStart_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() => PTT.ProductionTest());
}
This is the task from the PTT class, it will manage and monitor the 10 tasks so the UI is still responsive
public static void ProductionTest()
{
//Somewhere to store the tasks
List<Task> TestSlotTasks = new List<Task>();
//Create the tasks for each of the test slots
for (int i = 0; i != 9; ++i)
{
TestSlotTasks.Add(Task.Factory.StartNew(() => new PTT().TestSlot(i)));
Program.ListBoxAddLine(string.Format("CALL[{0}]: task start/stop test", i), System.Drawing.Color.LawnGreen, i);
}
Task.WaitAll(TestSlotTasks.ToArray());
}
This is supposed to be a non static program so I can create multiple instances (so I can run it many times without issue, that is my intention anyway, right?)
private void TestSlot(int slot)
{
for (var z = 0; z != 10; ++z)
{
Program.ListBoxAddLine(string.Format("PTT[{0}]: task start/stop test", slot), System.Drawing.Color.LawnGreen, slot);
}
}
This is the invoke method i use to update the listBox. The list box is in a generic list something like this
//ListBoxs.Add(listBoxSlot1); //Done for each of the 10 list box's to put in a list
//This is the method that will add a line to the main text box and to the log file
public static void ListBoxAddLine(string TextToAdd, System.Drawing.Color textColor, int slot)
{
//Update the list box on the main screen
if (mainForm != null)
{
if (FormPTT.ListBoxs[slot].InvokeRequired)
{
FormPTT.ListBoxs[slot].Invoke((MethodInvoker)delegate ()
{
FormPTT.ListBoxs[slot].ForeColor = textColor;
FormPTT.ListBoxs[slot].TopIndex = FormPTT.ListBoxs[slot].Items.Add(TextToAdd);
});
}
else
{
FormPTT.ListBoxs[slot].ForeColor = textColor;
FormPTT.ListBoxs[slot].TopIndex = FormPTT.ListBoxs[slot].Items.Add(TextToAdd);
}
}
}
The output I get is pretty random if i go above 5 or 6 list box's for this example.
Using the above code, I get this
output in the list box's, ignore the other stuff
So its all ok up until slot 8, its shows the Call[7] task start/stop but nothing from the task call and 9 and 10 are ok, but I dont call it 10 times, so how is 10 getting updated? I dont pass 10? and where is 8's PTT[7]... output?
I assume I am just not understanding a few things about tasks...
This question already has answers here:
Accessing UI (Main) Thread safely in WPF
(3 answers)
Closed 8 years ago.
why this code wont work without Dispatcher.RunAsync and what does it do? without Dispatcher its throwing error at copying value to textv.Text " thats its on different thread"
async void Current_GeofenceStateChanged(GeofenceMonitor sender, object args)
{
var reports = sender.ReadReports();
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (var report in reports)
{
GeofenceState st = report.NewState;
Geofence gf2 = report.Geofence;
if (st == GeofenceState.Entered)
{
textv2.Text = "Hello"; //XAML TEXT
}
else if(st==GeofenceState.Exited)
{
textv2.Text = "Bye";
}
}
});
}
The Event Current_GeofenceStateChanged is being fired outside of the GUI thread and only the GUI thread can change GUI elements. Dispatcher.RunAsync says the code inside should run on the GUI thread so it works.
if you put the result on a string variable it will work if you only put:
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => textv2.Text = StringVariable;);
EDIT: I only noticed that you have XAML code later you can just put the string on a property and bind the property to the text value of the text box letting you free from the Dispatcher.
<TextBox Text="{Binding StringVariable}"/>
and on the code just have
public string StringVariable { get; set; }
than on the method just set the value to the property
StringVariable = "bla bla";
This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
Closed 9 years ago.
I have a problem. I'm trying to make seekbar for BASS in C#.NET (WPF) but when I'm starting a new Thread it can't get access to modify value of my Slider or Label (with current song position value). Here is a Code:
//class:
Thread seekbar;
public Window1()
{
InitializeComponent();
Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
seekbar = new Thread(new ThreadStart(this.ThreadTask));
seekbar.IsBackground = true;
seekbar.Start();
}
private void ThreadTask()
{
int value = (int)Bass.BASS_ChannelGetPosition(music);
while (true)
{
MusicSeekBar.Value = value; //MusicSeekBar is mine Slider
CurrentValue.Content = value; //CurrentValue is a Label
}
}
I always get an error that thread can't get access to this two objects. Thanks for help in advice.
You need to call the MusicSeekBar & CurrentValue in a Dispatcher beceause the new thread doesn't have access to them.
Dispatcher.BeginInvoke(new Action(() =>
{
MusicSeekBar.Value = value; //MusicSeekBar is mine Slider
CurrentValue.Content = value; //CurrentValue is a Label
}));
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.