Why does adding to TextBox.Text during the loop take up more memory with each iteration?

Short question

I have a loop that runs 180,000 times. At the end of each iteration, it is supposed to add the results to the TextBox, which is updated in real time.

Using MyTextBox.Text += someValue leads to the fact that the application consumes huge amounts of memory, and after several thousand entries, it runs out of available memory.

Is there a more efficient way to add text to TextBox.Text 180,000 times?

Edit I really don't care about the result of this particular case, however I want to know why it looks like a bot memory, and if there is a more efficient way to add text to a text field.




Long-running (original) question

I have a small application that reads a list of ID numbers in a CSV file and generates a PDF report for each of them. After creating each PDF file, ResultsTextBox.Text added with the identification number of the report that was processed and that it was successfully processed. The process runs in the background thread, so the ResultsTextBox is updated in real time when the items are processed

I am currently running the application against 180,000 identification numbers, however, the memory that the application occupies grows exponentially with time. It starts at about 90 thousand, but at about 3000 records it takes about 250 MB, and for 4000 records the application takes about 500 MB of memory.

If I comment out the update for the TextBox of the results, the memory remains relatively fixed at about 90K, so I can assume that the entry ResultsText.Text += someValue is what makes it have memory.

My question is: why is this? What is the best way to add data to a TextBox.Text that does not eat memory?

My code is as follows:

 try { report.SetParameterValue("Id", id); report.ExportToDisk(ExportFormatType.PortableDocFormat, string.Format(@"{0}\{1}.pdf", new object[] { outputLocation, id})); // ResultsText.Text += string.Format("Exported {0}\r\n", id); } catch (Exception ex) { ErrorsText.Text += string.Format("Failed to export {0}: {1}\r\n", new object[] { id, ex.Message }); } 

It should also be noted that the application is one-time, and it does not matter that it takes several hours (or days :) to create all the reports. My main problem is that if it reaches the limit of system memory, it will stop working.

I'm fine, leaving a line updating the TextBox results commented out to run this thing, but I would like to know if there is a more efficient way to store data in TextBox.Text for future projects.

+81
c # wpf
Jan 04 2018-12-12T00:
source share
12 answers

I suspect that memory usage is so great because text files support the stack so that the user can cancel / repeat the text. This function does not seem to be required in your case, so try setting IsUndoEnabled to false.

+119
Jan 04 2018-12-21T00:
source share

Use TextBox.AppendText(someValue) instead of TextBox.Text += someValue . It is easy to skip because it is in a TextBox, not a TextBox.Text. Like StringBuilder, this will prevent you from creating copies of all the text every time you add something.

It would be interesting to see how this compares with the IsUndoEnabled flag with the response to the keyboard.

+14
Jan 10 2018-12-12T00:
source share

Do not add directly to the text property. Use StringBuilder to add, and then when done, set .text to the finished string from stringbuilder

+9
Jan 04 2018-12-12T00:
source share

Instead of using a text box, I would do the following:

  • Open a text file and, just in case, send errors to the log file.
  • Use a list control to represent errors to avoid copying potentially massive strings.
+5
Jan 04 2018-12-12T00:
source share

Personally, I always use string.Concat *. I remember reading a question here about Qaru several years ago, which profiled statistics, comparing commonly used methods, and (it seems) to remember that string.Concat won.

However, the best I can find is this reference question and this particular String.Format vs. StringBuilder , which mentions that String.Format uses StringBuilder internally. This makes me wonder if your memory is in another place.

** based on James comment, I should mention that I never do heavy formatting of strings, as I focus on web development. *

+4
Jan 04 2018-12-12T00:
source share

Maybe revise the TextBox? A string containing a ListBox Items are likely to work better.

But the main problem is the requirements. The indication of 180,000 elements cannot be aimed at the user (person) and does not change it in "real time".

A preferred way would be to show a sample of data or an indicator of progress.

When you want to reset it to a poor user, update the batch line. No user can describe more than 2 or 3 changes per second. Therefore, if you produce 100 / second, create groups of 50.

+3
Jan 04 2018-12-21T00:
source share

Some answers referred to him, but no one said this unexpectedly. Strings are immutable, which means that a string cannot be changed after it is created. Therefore, every time you connect to an existing string, you must create a new String object. Obviously, you need to create memory associated with this String object, which can become expensive as your strings get bigger and bigger. In college, I once made an amateur mistake when concatenating strings in a Java program that did Huffman encoding compression. When you combine extremely large amounts of text, string concatenation can really hurt you when you could just use StringBuilder, as some of them mention.

+2
Jan 04 2018-12-12T00:
source share

Use StringBuilder as suggested. Try to estimate the final string size, then use this number when creating an instance of StringBuilder. StringBuilder sb = new StringBuilder (estSize);

When updating a TextBox, just use the assignment, for example: textbox.text = sb.ToString ();

Watch for cross-streaming operations as above. However, use BeginInvoke. No need to block the background thread when updating the user interface.

+2
Jan 04 '12 at 23:27
source share

A) Intro: already mentioned, use StringBuilder

B) Point: do not update too often, i.e.

 DateTime dtLastUpdate = DateTime.MinValue; while (condition) { DoSomeWork(); if (DateTime.Now - dtLastUpdate > TimeSpan.FromSeconds(2)) { _form.Invoke(() => {textBox.Text = myStringBuilder.ToString()}); dtLastUpdate = DateTime.Now; } } 

C) If this is a one-time job, use the x64 architecture to stay within 2 GB.

+1
Jan 04 2018-12-12T00:
source share

StringBuilder in the ViewModel will avoid the mess of reordering strings and bind it to MyTextBox.Text . This scenario will increase performance many times over and decrease memory usage.

+1
Jan 04 '12 at 22:22
source share

Something that was not mentioned is that even if you perform an operation in a background thread, the update of the UI element itself should happen in the main thread (in WinForms).

When updating a text field, you have code that looks like

 if(textbox.dispatcher.checkAccess()){ textbox.text += "whatever"; }else{ textbox.dispatcher.invoke(...); } 

If so, then your background statement will definitely be a hindrance to updating the user interface.

I would suggest that your background statement uses a StringBuilder as above, but instead of updating the text field every loop, try updating it at regular intervals to see if it improves performance for you.

EDIT NOTE: did not use WPF.

0
Jan 04 2018-12-12T00:
source share

You say that memory grows exponentially. No, this is a quadratic growth , i.e. The growth of a polynomial, which is not as dramatic as exponential growth.

You create strings containing the following number of elements:

 1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2. 

With n = 180,000 you get the total memory allocation for 16,200,090,000 items , i.e. 16.2 billion items ! This memory will not be allocated immediately, but there is a lot of cleaning work for the GC (garbage collector)!

Also note that the previous line (which is growing) must be copied to a new line 179.999 times. The total number of bytes copied goes with n^2 !

As others suggested, use a ListBox instead. Here you can add new lines without creating a huge line. A StringBuild does not help, since you also want to display intermediate results.

0
Apr 18 '17 at 12:23
source share



All Articles