|
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.
|
|
|
|
|
I am a bit confused. Can you explain it pls if u dont mind?
modified 1-Aug-19 21:02pm.
|
|
|
|
|
EARNEST_OLEK wrote: I am a bit confused. Can you explain it pls if u dont mind?
OK, I think i got the idea...thanks, i will try to re-implement my program, Thanks. I will post the updates
modified 1-Aug-19 21:02pm.
|
|
|
|
|
I've read certain articles several times. Implemented it as it says, but still can't fix the problem
modified 1-Aug-19 21:02pm.
|
|
|
|
|
I wrote an article here last week that addresses this:
Multithreading, Delegates, and Custom Events[^].45 ACP - because shooting twice is just silly ----- "Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997 ----- "The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001
|
|
|
|
|
Hey, guys. I think i managed to sovle, but I've got few questions.
My solution:
private void Initiate_ANN_Training()
{
MethodInvoker guiUpdateDuringLearning = new MethodInvoker(ThreadDynamic);
MethodInvoker guiUpdateAfterLearning = new MethodInvoker(AfterLearning);
SettingsInfoDisplay();
Options();
trainingANN_instance = new ANN_Training();
Invoke(guiUpdateDuringLearning);
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);
Invoke(guiUpdateAfterLearning);
}
For events methods:
private void trainingANN_instance_RatioSynch(double variable)
{
if (this.InvokeRequired)
{
BeginInvoke(new DelegateToCrossThread_Double(trainingANN_instance_RatioSynch), new object[] { variable });
return;
}
else
{
this.tbRatio.Text = "" + variable;
}
}
Question 1: BeginInvoke invokes asynch. What does it mean? If thread works on variable X and its current value 5, would it reflect value 5 or something else? My goal is to show real-time current information. Does BeginInvoke compromise this issue?
Question 2: What are other solutions to my "problem"? Would like to know alternative ways.
Question 3: At some point, if I leave my code as it was, except Initiate_ANN_Learning method, removing InvokeRequired from it, I get this error:
System.Reflection.TargetParameterCountException was unhandled
Message="Parameter count mismatch."
Source="System.Windows.Forms"
StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at ANN_Project.ANN_GUI.trainingANN_instance_RatioSynch(Double variable) in C:\Users\EARNEST\Documents\Visual Studio 2008\Projects\ANN_take2\ANN_take2\ANN_GUI.cs:line 541
at ANN_Project.ANN_Training.Training(Int32 inputNeurons, Int32 hiddenNeurons, Int32 outputNeurons, Double errorThreshold, Double learning, Int32 optionSel, Boolean dynamicLogOption) in C:\Users\EARNEST\Documents\Visual Studio 2008\Projects\ANN_take2\ANN_take2\ANN_Training.cs:line 255
at ANN_Project.ANN_GUI.Initiate_ANN_Training() in C:\Users\EARNEST\Documents\Visual Studio 2008\Projects\ANN_take2\ANN_take2\ANN_GUI.cs:line 368
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
in my old
private void trainingANN_instance_RatioSynch(double variable)
{
if (this.InvokeRequired)
{
DelegateToCrossThread_Double del = new DelegateToCrossThread_Double(trainingANN_instance_BadFactsForGraphSynch);
this.Invoke(del);
}
else
{
this.tbRatio.Text = "" + variable;
}
}
Can some1 explain it pls?
Final question: Can some1 explain pls why MethodInvoke , BeginInvoke solved the problem, while InvokeRequired approach was useless?
Appreciate your input and understanding!
modified 1-Aug-19 21:02pm.
|
|
|
|
|
I have some performance issues. If I start moving when the calculation thread is activated, the textbox updates can freeze or hang up and then "jump" to new values, let's say it stopped updating on 23, then it will not respond for a second, and then will jump to 59 or so. Also, sometimes my application acts as if there is no multithreading there Does not respond
modified 1-Aug-19 21:02pm.
|
|
|
|
|
greetings, i am developing a battleships clone game and i have an issue with TableLayoutPanel MouseLeave event.
first MouseMove:
private PictureBox HomeLastPicBox = new PictureBox();
private TableLayoutPanelCellPosition homeLastPosition = new TableLayoutPanelCellPosition(0, 0);
private void HomeTableLayoutPanel_MouseMove(object sender, MouseEventArgs e)
{
PictureBox NowPicControl = (PictureBox)(HomeTableLayoutPanel.GetChildAtPoint(e.Location));
if ((NowPicControl != null) && (NowPicControl != HomeLastPicBox))
{
HomeLastPicBox = (PictureBox)(HomeTableLayoutPanel.GetControlFromPosition(homeLastPosition.Column, homeLastPosition.Row));
if (GameModel.HomeCellStatus(homeLastPosition.Column, homeLastPosition.Row) == Cell.cellState.WATER)
{
HomeLastPicBox.Image = Properties.Resources.water;
}
TableLayoutPanelCellPosition homeCurrentPosition = HomeTableLayoutPanel.GetCellPosition(NowPicControl);
if (GameModel.HomeCellStatus(homeCurrentPosition.Column, homeCurrentPosition.Row) == Cell.cellState.WATER)
{
NowPicControl.Image = Properties.Resources.scan;
}
homeLastPosition = homeCurrentPosition;
}
}
this appears to function properly.
now the MouseLeave event:
private void HomeTableLayoutPanel_MouseLeave(object sender, EventArgs e)
{
MessageBox.Show("col " + homeLastPosition.Column.ToString() + " row " + homeLastPosition.Row.ToString());
if (GameModel.HomeCellStatus(homeLastPosition.Column, homeLastPosition.Row) == Cell.cellState.WATER)
{
HomeLastPicBox.Image = Properties.Resources.water;
MessageBox.Show("hi");
}
HomeLastPicBox = new PictureBox();
}
this is acting strange. it goes through the code and even a "HI" is displayed but the PictureBox image is not changed to water. any ideas as to why? this does not happen all the time, only from time to time.
what the above code is doing is basically scanning through the table cells and if the cell content is WATER then it updates the table cell image to SCAN and as the user moves onwards it is switching the cell image back to WATER.
hope this is enough information. please ask if more is needed.
thank you in advance.
|
|
|
|
|
<pre>
if (GameModel.HomeCellStatus(homeLastPosition.Column, homeLastPosition.Row) == Cell.cellState.WATER)
{
HomeLastPicBox.Image = Properties.Resources.water;
MessageBox.Show("hi");
}
</pre>
i wonder why are you using this
<pre>HomeLastPicBox = new PictureBox();</pre>
when you have already assigned the picture to this picture box in the if block. after displaying the message box you need to return.
do it like this.
<pre>
if (GameModel.HomeCellStatus(homeLastPosition.Column, homeLastPosition.Row) == Cell.cellState.WATER)
{
HomeLastPicBox.Image = Properties.Resources.water;
MessageBox.Show("hi");
return;
}
hope it helps.
</pre>
Ahsan Ullah
Senior Software Engineer
MCTS 2.0
|
|
|
|
|
no thats not going to work. i have made some progress. the reason im experiencing this behaviour is because mousemove is being called after the call of mouseleave.
|
|
|
|
|
I have created a C# application that has 3 modules so far, default.aspx, registration.aspx and supplierinfo.aspx all using code_behind. I then created a class dbConnect.cs that I will use to connect to a DB. I am able to instatiate the dbConnect class in supplierinfo.aspx.cs and get the connection string. In the other 2 modules I am unable to instatiate the dbConnect class. I enter:
var dbCon = new dbConnect(); OR dbConnect dbCon = new dbConnect;
and then do a build. I am getting the following error:
Error 35 The type or namespace name 'dbConnect' could not be found (are you missing a using directive or an assembly reference?)
I can't see any differences in the modules that would cause this. Has anyone run into this before or have any suggestions what to look for?
Thanks for the help
|
|
|
|
|