Pregunta corta
Tengo un bucle que se ejecuta 180.000 veces. Al final de cada iteración, se supone que debe agregar los resultados a un TextBox, que se actualiza en tiempo real.
El uso MyTextBox.Text += someValue
hace que la aplicación consuma grandes cantidades de memoria y se quede sin memoria disponible después de unos pocos miles de registros.
¿Existe una forma más eficiente de agregar texto a TextBox.Text
180.000 veces?
Editar Realmente no me importa el resultado de este caso específico, sin embargo, quiero saber por qué esto parece ser una pérdida de memoria y si hay una forma más eficiente de agregar texto a un TextBox.
Pregunta larga (original)
Tengo una pequeña aplicación que lee una lista de números de identificación en un archivo CSV y genera un informe en PDF para cada uno. Después de que se genera cada archivo pdf, ResultsTextBox.Text
se agrega el número de identificación del informe que se procesó y que se procesó correctamente. El proceso se ejecuta en un hilo de fondo, por lo que ResultsTextBox se actualiza en tiempo real a medida que se procesan los elementos
Actualmente estoy ejecutando la aplicación con 180.000 números de identificación, sin embargo, la memoria que ocupa la aplicación está creciendo exponencialmente a medida que pasa el tiempo. Comienza por alrededor de 90K, pero por alrededor de 3000 registros ocupa aproximadamente 250 MB y por 4000 registros la aplicación está ocupando alrededor de 500 MB de memoria.
Si comento la actualización del Results TextBox, la memoria permanece relativamente estacionaria en aproximadamente 90K, por lo que puedo asumir que la escritura ResultsText.Text += someValue
es lo que está causando que se coma la memoria.
Mi pregunta es, ¿por qué es esto? ¿Cuál es una mejor manera de agregar datos a un TextBox.Text que no consume memoria?
Mi código se ve así:
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 });
}
También vale la pena mencionar que la aplicación es algo que se usa una sola vez y no importa que se necesiten algunas horas (o días :)) para generar todos los informes. Mi principal preocupación es que si alcanza el límite de memoria del sistema, dejará de funcionar.
Estoy bien con dejar la línea actualizando el Results TextBox comentado para ejecutar esto, pero me gustaría saber si hay una forma más eficiente de agregar datos a la memoria TextBox.Text
para proyectos futuros.
StringBuilder
para agregar el texto y luego, una vez completado, asignar elStringBuilder
valor al cuadro de texto.Respuestas:
Sospecho que la razón por la que el uso de memoria es tan grande es porque los cuadros de texto mantienen una pila para que el usuario pueda deshacer / rehacer el texto. Esa característica no parece ser necesaria en su caso, así que intente configurarla
IsUndoEnabled
en falso.fuente
UndoLimit
un valor realista. El valor predeterminado de -1 indica una pila ilimitada. Cero (0) también deshabilitará deshacer.Utilizar en
TextBox.AppendText(someValue)
lugar deTextBox.Text += someValue
. Es fácil pasarlo por alto ya que está en TextBox, no en TextBox.Text. Al igual que StringBuilder, esto evitará crear copias de todo el texto cada vez que agregue algo.Sería interesante ver cómo se compara esto con la
IsUndoEnabled
bandera de la respuesta de keyboardP.fuente
bool CanUndo
propiedadNo agregue directamente a la propiedad de texto. Use un StringBuilder para agregar, luego, cuando haya terminado, establezca el .text en la cadena terminada del stringbuilder
fuente
En lugar de usar un cuadro de texto, haría lo siguiente:
fuente
Personalmente, siempre uso
string.Concat
*. Recuerdo haber leído una pregunta aquí en Stack Overflow hace años que tenía estadísticas de perfiles comparando los métodos de uso común, y (parece) recordar questring.Concat
ganó.No obstante, lo mejor que puedo encontrar es esta pregunta de referencia y esta pregunta específica
String.Format
vs.StringBuilder
, que menciona queString.Format
usaStringBuilder
internamente. Esto me hace preguntarme si tu memoria está en otra parte.** basado en el comentario de James, debo mencionar que nunca hago un gran formato de cadenas, ya que me enfoco en el desarrollo basado en la web. *
fuente
string.Format
/StringBuilder
es más apropiado aquí?¿Quizás reconsiderar el TextBox? Un ListBox con elementos de cadena probablemente funcionará mejor.
Pero el problema principal parecen ser los requisitos. Mostrar 180.000 elementos no puede estar dirigido a un usuario (humano), ni cambiarlo en "Tiempo real".
La forma preferible sería mostrar una muestra de los datos o un indicador de progreso.
Cuando desee volcarlo en el usuario pobre, actualice la cadena por lotes. Ningún usuario pudo distinguir más de 2 o 3 cambios por segundo. Entonces, si produce 100 / segundo, haga grupos de 50.
fuente
Algunas respuestas lo han aludido, pero nadie lo ha declarado abiertamente, lo cual es sorprendente. Las cadenas son inmutables, lo que significa que una cadena no se puede modificar después de su creación. Por lo tanto, cada vez que concatena a una cadena existente, es necesario crear un nuevo objeto de cadena. Obviamente, la memoria asociada con ese objeto String también debe crearse, lo que puede resultar costoso a medida que sus cadenas se hacen cada vez más grandes. En la universidad, una vez cometí el error de aficionado de concatenar cadenas en un programa Java que hacía compresión de codificación Huffman. Cuando está concatenando cantidades extremadamente grandes de texto, la concatenación de cadenas puede realmente perjudicarlo cuando podría haber usado simplemente StringBuilder, como algunos han mencionado aquí.
fuente
Utilice StringBuilder como se sugiere. Intente estimar el tamaño final de la cadena y luego use ese número al crear una instancia de StringBuilder. StringBuilder sb = nuevo StringBuilder (estSize);
Cuando actualice el TextBox, simplemente use la asignación, por ejemplo: textbox.text = sb.ToString ();
Esté atento a las operaciones de subprocesos cruzados como arriba. Sin embargo, use BeginInvoke. No es necesario bloquear el hilo de fondo mientras se actualiza la interfaz de usuario.
fuente
A) Intro: ya mencionado, use
StringBuilder
B) Punto: no actualice con demasiada frecuencia, es decir
DateTime dtLastUpdate = DateTime.MinValue; while (condition) { DoSomeWork(); if (DateTime.Now - dtLastUpdate > TimeSpan.FromSeconds(2)) { _form.Invoke(() => {textBox.Text = myStringBuilder.ToString()}); dtLastUpdate = DateTime.Now; } }
C) Si es un trabajo único, use la arquitectura x64 para mantenerse dentro del límite de 2 Gb.
fuente
StringBuilder
inViewModel
evitará el desorden de las reencuadernaciones de cuerdas y las uniráMyTextBox.Text
. Este escenario aumentará el rendimiento muchas veces y disminuirá el uso de memoria.fuente
Algo que no se ha mencionado es que incluso si está realizando la operación en el subproceso en segundo plano, la actualización del elemento de la interfaz de usuario TIENE que suceder en el subproceso principal (en WinForms de todos modos).
Al actualizar su cuadro de texto, ¿tiene algún código que se parezca a
if(textbox.dispatcher.checkAccess()){ textbox.text += "whatever"; }else{ textbox.dispatcher.invoke(...); }
Si es así, la actualización de la interfaz de usuario definitivamente está obstruyendo su operación en segundo plano.
Sugeriría que su operación en segundo plano use StringBuilder como se indicó anteriormente, pero en lugar de actualizar el cuadro de texto en cada ciclo, intente actualizarlo a intervalos regulares para ver si aumenta el rendimiento para usted.
EDITAR NOTA: no he usado WPF.
fuente
Dices que la memoria crece exponencialmente. No, es un crecimiento cuadrático , es decir, un crecimiento polinomial, que no es tan dramático como un crecimiento exponencial.
Está creando cadenas con el siguiente número de elementos:
1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2.
Con
n = 180,000
usted obtiene la asignación total de memoria para16,200,090,000 items
, es decir16.2 billion items
! Esta memoria no se asignará a la vez, ¡pero es mucho trabajo de limpieza para el GC (recolector de basura)!Además, tenga en cuenta que la cadena anterior (que está creciendo) debe copiarse en la nueva cadena 179,999 veces. ¡El número total de bytes copiados también va con él
n^2
!Como han sugerido otros, use un ListBox en su lugar. Aquí puede agregar nuevas cadenas sin crear una cadena enorme. A
StringBuild
no ayuda, ya que también desea mostrar los resultados intermedios.fuente