|
Hmmm...
Thanks, Nick. I will try read more about threading. As soon as i manage to solve it, i will post the results here.
-- modified 1-Aug-19 21:02pm.
|
|
|
|
|
Sorry if I was a bit terse. There's a lot to learn about concurrent software and some people never "get" it.
Joe Albahari's ebook is a good start: http://www.albahari.com/threading/[^]
Jon Skeet's version is also quite good: http://www.yoda.arachsys.com/csharp/threads/[^]
If you are really interested, I recommend Joe Duffy's book: "Concurrent Programming on Windows". It's 1000 pages long, but every page is filled with knowledge. It's on Safari, if you're a member.
Nick----------------------------------
Be excellent to each other
|
|
|
|
|
Nicholas Butler wrote: It's on Safari
When will it be off Safari or will I have to go to Africa to read it?Why is common sense not common?
Never argue with an idiot. They will drag you down to their level where they are an expert.
Sometimes it takes a lot of work to be lazy
Individuality is fine, as long as we do it together - F. Burns
Help humanity, join the CodeProject grid computing team here
|
|
|
|
|
Geesh some people with no sense of humor...Why is common sense not common?
Never argue with an idiot. They will drag you down to their level where they are an expert.
Sometimes it takes a lot of work to be lazy
Individuality is fine, as long as we do it together - F. Burns
Help humanity, join the CodeProject grid computing team here
|
|
|
|
|
I agree with Nick's remarks, and I suggest you read my little article[^]; you already got a lot of it right, but I don't understand your snippet collection sufficiently to confirm it is all right overall.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
Luc Pattyn wrote:
my little article[^]
Didn't manage to solve a problem using your article Kinda already did that
-- modified 1-Aug-19 21:02pm.
|
|
|
|
|
yes, I noticed some InvokeRequired, however I could not ascertain you did it everywhere.
And you mentioned performance problems, without much detail, and I discussed that somewhat too.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
The thing is that if logOption is set to false, the events and delegates are not going to be activated, boosting the speed of calculations (as intended). However, If i will not add
Application.DoEvents()
to
private void trainingANN_instance_LogReportSynch(string stringname)
{
if (this.InvokeRequired)
{
DelegateToCrossThread_String del = new DelegateToCrossThread_String(trainingANN_instance_LogReportSynch);
this.Invoke(del);
}
else
{
this.tbVeryBig.Text += stringname;
}
}
that text box wont be updated. Also, if logoption is false, these do no appear in the textbox, however, console shows them
this.tbVeryBig.Text += "[" + DateTime.Now.TimeOfDay.ToString() + "]: txt [" + inN+ "]\r\n";
modified 1-Aug-19 21:02pm.
|
|
|
|
|
Hi,
1.
Needing Applications.DoEvents() most often is an indication you did something wrong, such as having event handlers that take too much time (more than a few dozen milliseconds). And it is dangerous when being called from inside an event handler as it turns such handler into re-enterable code which you probably did not intend to do.
2.
You showing snippets and hopping around does not really help to clarify things, however I notice you use a TextBox for logging, which IMO is a bad idea, as TB and RTB keep all text lines in one, always growing, string, requiring lots of allocations, copies, and garbage collections. I strongly advice to log in a ListBox, where each new log message simply is one more entry in the Items collection.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
Thanks, appreciate your input, however I do not really know what to point out exactly. My speculation is that after cross-threading my program, the events cannot occur as they used to before
Thanks for the suggestion about LB's.
Maybe you could give some hints how I can clarify the problem?
modified 1-Aug-19 21:02pm.
|
|
|
|
|
My clarification suggestions:
- Don't change identifier names, especially method names; methodA, methodB, ... don't make an easy reading, besides they never got called anywhere.
- Also show relevant code together with the method declaration it is in.
- And please explain what your app does, so the reader can get a mental picture of the overall code.
- Maybe explain the overall structure (methods, possibly threads) using pseudo-code (with the actual names if possible).
- you complain about performance; please explain. Maybe title bar says "... (Not Responding)"? Maybe output that came at 100 lines per second now comes at 3 lines per second. What?
Other suggestions:
- each InvokeRequired/Invoke is likely to cause a thread switch, i.e. waste a few microseconds. Sending millions of updates to a Control (say a ProgressBar) which might have only a very limited number of states (0 to 100%) is to be avoided; maybe update only 1 out of 1000 in a loop, or only every 100 milliseconds, or...
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
Also, not sure if you understood why I used a textbox. I would like to output certain information there.
modified 1-Aug-19 21:02pm.
|
|
|
|
|
A TextBox is fine for showing and for editing a limited amount of text, no more than say 50 lines. It slows down quadratically as the text grows.
A ListBox is great for showing anything that is itemized, such as log lines. It does not slow down when the content increases, assuming it all fits in physical memory.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
I've been successfully using a TextBox to output strings as large as 100 MB. They perform well enough if you know how to access them correctly.
Remember the class is just a thin wrapper around the Win32 control. The string is stored in native memory, and appending stuff does not necessarily involve memory allocation - the native code will store text in some buffer appropriate for editing.
What kills performance is accessing the TextBox.Text property - the whole string is copied from the native buffer to managed memory every time the property is read, and back every time it is written.
As a result, using a TextBox.AppendText(x) is much more performant than using TextBox.Text += x; .
You will also want to avoid too many small append calls. Instead, let the thread doing the logging write into a StringBuilder , and only call into the GUI thread a single time to start a timer that collects the data from the StringBuilder after 250ms or so. Make sure you synchronize access to the StringBuilder and don't get into race conditions controlling the timer (easiest solution: just keep the timer enabled all the time). Doing lock (loggerLock) loggerBuilder.Append(x); is way faster than calling Control.BeginInvoke .
Also remember that logging to a text box is essentially an asynchronous process - there's no need for the background operation to wait until the text box has received the data. Never use Control.Invoke when the faster Control.BeginInvoke is sufficient.
Done correctly you can log several megabytes of text per second into a TextBox.
|
|
|
|
|
Thanks, that is very informative.
1.
It is too bad MSDN didn't provide the clue; rather than just saying "[AppendText:] You can use this method to add text to the existing text in the control instead of using the concatenation operator (+) to concatenate text to the Text property." it should have mentioned performance reasons for using AppendText().
2.
I understand your timer+StringBuilder+lock approach is more performant, it also is more complex, and typically will result in some extra latency, which makes it harder to use to diagnose fatal crashes where one wants to see the most recent messages available. For logging I tend to prefer synchronous operations without extra latency, even when that reduces overall performance a bit; a ListBox and Control.Invoke serve me well. But I will experiment with what you suggested.
3.
When using the InvokeRequired/Invoke pattern, I hesitate to use Control.BeginInvoke() as that modifies the semantics of the method: when on the right thread, it works synchronously, whereas on another thread it only executes asynchronously. I am aware the thread switches are expensive though.
I may conduct several experiments on all this and create a little article on the subject.
Again, thanks.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|
Luc Pattyn wrote: When using the InvokeRequired/Invoke pattern, I hesitate to use Control.BeginInvoke() as that modifies the semantics of the method: when on the right thread, it works synchronously, whereas on another thread it only executes asynchronously. I am aware the thread switches are expensive though.
No. If you don't check InvokeRequired but simply use BeginInvoke every time, it will always be asynchronously. If the code is already running on the GUI thread, the delegate won't be executed immediately but enqueued in the message loop.
Using BeginInvoke on the GUI thread is a way to say "call this later", similar to "call this on next Idle event" (but I think it has much higher priority than Idle).
I don't know where the recommendation to check InvokeRequired came from, but everyone seems to copy it whenever writing about Invoke/BeginInvoke. I never use InvokeRequired that way; I use it only in the form Debug.Assert(!InvokeRequired); .
- Checking InvokeRequired when calling Invoke is useless (Invoke already does that).
- Checking it when calling BeginInvoke is dangerous as it changes semantics (makes asynchronous call synchronous).
Add to that that InvokeRequired can produce false negatives (if the control handle hasn't been created yet), and checking InvokeRequired is highly dangerous for anything but safety checks.
|
|
|
|
|
Very interesting approach.
Let me make clear what I am doing. I am trying to build an ANN (neural net).
1. TextBox is used for displaying information in real time, i.e. the number of training pairs, the starting time, end time, etc. This is just for me to keep track, plus it looks cool
2. A class with all the GUI controls and a class with the main training method. A delegate and event in the training class serve as the synch update of the information (also for number 1). e.g.
trainingANN_instance_LogReportSynch
3. I am not very familiar with threads, I've tired to use the approach with InvokeRequired , but I cannot understand why my events do not work. As an example:
private void butTraining_MouseClick(object sender, MouseEventArgs e)
{
this.trainingANN_thread = new Thread(new ThreadStart(this.Initiate_ANN_Training));
LockOptions();
this.tbVeryBig.Clear();
this.trainingANN_thread.Start();
this.tbVeryBig.Text += "[" + DateTime.Now.TimeOfDay.ToString() + "]: INITIALIZATION\r\n";
}
private void SettingsInfoDisplay()
{
if (this.InvokeRequired)
{
DelegateToCrossThread_None crossThreader = new DelegateToCrossThread_None(SettingsInfoDisplay);
this.Invoke(crossThreader);
}
else
{
this.tboxOutputNeuron.Text = "" + outputNeurons;
this.tboxInputNeuron.Text = "" + inputNeurons;
this.tboxHiddenNeuron.Text = "" + tbarHiddenN.Value;
this.tboxErrorTreshold.Text = "" + (this.tbarErrorTreshold.Value / 100.0);
this.tboxTrainingRate.Text = "" + (this.tbarTrainingRate.Value / 100.0);
this.tbBadF.Text = "<not defined>";
this.tbAccuracy.Text = "<not defined>";
this.tbConvergeRate.Text = "<not defined>";
this.tbErrorCounter.Text = "<not defined>";
this.tbRatio.Text = "<not defined>";
this.tbGoodF.Text = "<not defined>";
this.tbEpoch.Text = "<not defined>";
}
}
private void Options()
{
if (this.InvokeRequired)
{
DelegateToCrossThread_None crossThreader = new DelegateToCrossThread_None(Options);
this.Invoke(crossThreader);
}
else
{
errorTreshold = (double)((tbarErrorTreshold.Value) / 100.0);
learningRate = (double)((tbarTrainingRate.Value) / 100.0);
hiddenNeurons = tbarHiddenN.Value;
if (this.cboxLog.Checked != true)
{
logOption = false;
}
else
logOption = true;
}
}
private void Initiate_ANN_Training()
{
if (this.InvokeRequired)
{
DelegateToCrossThread_None crossThreader = new DelegateToCrossThread_None(Initiate_ANN_Training);
this.Invoke(crossThreader);
}
else
{
SettingsInfoDisplay();
Options();
this.tbVeryBig.AppendText("text");
trainingANN_instance = new ANN_Training();
this.tbVeryBig.Text += "[" + DateTime.Now.TimeOfDay.ToString() + "]: ANN TRAINING STARTED!\r\n";
trainingANN_instance.GoodFactsSynch += new StatsSynch(trainingANN_instance_GoodFactsSynch);
trainingANN_instance.BadFactsSynch += new StatsSynch(trainingANN_instance_BadFactsSynch);
trainingANN_instance.RatioSynch += new StatsSynch(trainingANN_instance_RatioSynch);
trainingANN_instance.ConvergeRateSynch += new StatsSynch(trainingANN_instance_ConvergeRateSynch);
trainingANN_instance.EpochSynch += new StatsSynch(trainingANN_instance_EpochSynch);
trainingANN_instance.LogReportSynch += new StringSynch(trainingANN_instance_LogReportSynch);
trainingANN_instance.BadFactsForGraphSynch += new StatsSynch(trainingANN_instance_BadFactsForGraphSynch);
trainingANN_instance.Training(inputNeurons, hiddenNeurons, outputNeurons, errorTreshold, learningRate, progressFilter, logOption);
this.tbVeryBig.Text += "[" + DateTime.Now.TimeOfDay.ToString() + "]: ANN TRAINING FINISHED!\r\n";
double ratio = (trainingANN_instance.good / trainingANN_instance.bad * 100);
this.tbRatio.Text = (Math.Round(ratio, 5)) + "%";
this.tbEpoch.Text = trainingANN_instance.epoch.ToString();
this.tbBadF.Text = trainingANN_instance.bad.ToString();
this.tbGoodF.Text = trainingANN_instance.good.ToString();
this.tbConvergeRate.Text = Math.Round((trainingANN_instance.progressRatio_temp), 5).ToString() + "%";
this.tbVeryBig.Text += "[" + DateTime.Now.TimeOfDay.ToString() + "]: RESULTS:\r\n EPOCH: " + trainingANN_instance.epoch.ToString();
this.tbVeryBig.Text += "\r\n BAD FACTS: " + trainingANN_instance.bad.ToString();
this.tbVeryBig.Text += "\r\n GOOD FACTS: " + trainingANN_instance.good.ToString();
this.tbVeryBig.Text += "\r\n RATIO: " + ratio + "\r\n CONVERGE RATE: " + Math.Round((trainingANN_instance.progressRatio_temp), 5).ToString() + "%";
UnlockOptions();
trainingANN_thread.Abort();
}
}
private void trainingANN_instance_RatioSynch(double variable)
{
if (this.InvokeRequired)
{
DelegateToCrossThread_Double del = new DelegateToCrossThread_Double(trainingANN_instance_RatioSynch);
this.Invoke(del);
}
else
{
this.tbRatio.Text = "" + variable;
}
}
So as you can see i commented App.DoEvents(). Without that line, events do not work, using my approach.
Another observation. When logOption was false or no DoEvents() in the code present, and textbox used to be updated with the += approach, the application used to freeze for certain seconds and only show the output after the training method of class 2 was done, however, if i try to display that info using Console.Write(), it worked fine. However, now, with new ideas with AppendText() , it works without problems.
modified 1-Aug-19 21:02pm.
|
|
|
|
|
All your calculations are still running on the main thread.
You start a new thread for Initiate_ANN_Training , but that method than immediately invokes back to the main thread. You're running all calculations on the GUI thread, just as if you did not create a new thread in the first place.
You should call Invoke only for the operations that need access to the GUI (accessing text box controls), but not for your expensive calculation (I'm guessing that's the trainingANN_instance.Training call?)
|
|
|
|
|
Yes, you are right. So how would you recommend going about it? Any hints pls? Should I separate the training call?
modified 1-Aug-19 21:02pm.
|
|
|
|
|
Split Initiate_ANN_Training into 3 methods: initialization, training, displaying results.
Use Invoke only in those methods that need to access the GUI: initialization, displaying results, and the logging events.
|
|
|
|
|
Did you mean something like that
private void Initiate_ANN_Training()
{
SettingsInfoDisplay();
Options();
DisplayInformation();
trainingANN_instance = new ANN_Training();
ANN_Events();
StartTraining();
?
modified 1-Aug-19 21:02pm.
|
|
|
|
|
A TextBox is fine for logging output. It also allows the user to copy out text blocks (which isn't easily possible with a ListBox ).
Please see my reply to Luc on how to improve performance when logging to a TextBox.
You've made two mistakes that dramatically slow down your code (using += on strings and letting the background operation wait for the textbox).
|
|
|
|
|
Thanks a lot, very helpful. Can you explain a bit more about
Daniel Grunwald wrote: letting the background operation wait for the textbox). , please.
The first one (+= part) as far as I understood is better to implement via .Append(x).
Thanks a lot. Still learning Appreciate all your help, guys!
modified 1-Aug-19 21:02pm.
|
|
|
|
|
When you call Invoke(someDelegate); from a thread other than the GUI thread, it will send the message "please execute this delegate" to the GUI and wait until that message was processed.
That means your calculation thread is waiting for the GUI to repaint the TextBox after displaying the message!
Also, it is much faster to group several log messages and append them in a single AppendText call.
So my proposed solution was to let the background thread add log messages to a some kind of buffer (e.g. StringBuilder). A timer on the GUI thread then regularly moves the text from the buffer to the TextBox.
readonly object loggerLock = new object();
StringBuilder loggerBuffer = new StringBuilder();
private void trainingANN_instance_LogReportSynch(string stringname)
{
lock (loggerLock) {
loggerBuffer.Append(stringname);
}
}
void logTimer_Tick(object sender, EventArgs e)
{
string newText;
lock (loggerLock) {
newText = loggerBuffer.ToString();
loggerBuffer.Length = 0;
}
if (newText.Length > 0)
tbVeryBig.AppendText(newText);
}
|
|
|
|
|
Yes, with a System.Windows.Forms.Timer one can avoid all Invoke() calls, which is good.
Luc Pattyn [Forum Guidelines] [Why QA sucks] [My Articles]
I only read code that is properly formatted, adding PRE tags is the easiest way to obtain that. All Toronto weekends should be extremely wet until we get it automated in regular forums, not just QA.
|
|
|
|
|