Pourquoi l’ajout de TextBox.Text au cours d’une boucle prend-il plus de mémoire à chaque itération?

Courte question

J’ai une boucle qui fonctionne 180 000 fois. À la fin de chaque itération, il est censé append les résultats à une zone de texte, qui est mise à jour en temps réel.

L’utilisation de MyTextBox.Text += someValue amène l’application à consumr d’énormes quantités de mémoire et à court de mémoire disponible après quelques milliers d’enregistrements.

Existe-t-il un moyen plus efficace d’append du texte à un TextBox.Text 180 000 fois?

Edit Je ne me soucie vraiment pas du résultat de ce cas particulier, mais je veux savoir pourquoi cela semble être un problème de mémoire, et s’il existe un moyen plus efficace d’append du texte à une zone de texte.


Question longue (originale)

J’ai une petite application qui lit une liste de numéros d’identification dans un fichier CSV et génère un rapport PDF pour chacun. Une fois que chaque fichier pdf est généré, ResultsTextBox.Text est ajouté avec le numéro d’identification du rapport qui a été traité et qu’il a été traité avec succès. Le processus s’exécute sur un thread d’arrière-plan, ce qui permet à ResultsTextBox d’être mis à jour en temps réel à mesure que les éléments sont traités.

Je lance actuellement l’application contre 180 000 numéros d’identification, mais la mémoire que l’application prend est en croissance exponentielle au fil du temps. Il commence à environ 90K, mais à environ 3000 enregistrements, il prend environ 250 Mo et à 4000 enregistrements, l’application prend environ 500 Mo de mémoire.

Si je commente la mise à jour de la TextBox Results, la mémoire rest relativement stationnaire à environ 90K, donc je peux supposer que la rédaction de ResultsText.Text += someValue est ce qui la fait manger la mémoire.

Ma question est, pourquoi est-ce? Quelle est la meilleure façon d’append des données à un TextBox.Text qui ne mange pas de mémoire?

Mon code ressemble à ceci:

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

Il convient également de mentionner que l’application est ponctuelle et qu’il n’importe pas que cela prenne quelques heures (ou quelques jours) pour générer tous les rapports. Ma principale préoccupation est que si elle atteint la limite de mémoire système, elle cessera de fonctionner.

Je suis d’accord pour laisser la ligne mettre à jour les résultats TextBox commentés pour exécuter cette chose, mais je voudrais savoir s’il existe un moyen plus efficace d’append de la mémoire à un TextBox.Text pour des projets futurs.

Je soupçonne que la raison pour laquelle l’utilisation de la mémoire est si importante est que les boîtes de texte maintiennent une stack pour que l’utilisateur puisse annuler / rétablir le texte. Cette fonctionnalité ne semble pas être requirejse dans votre cas, essayez donc de définir IsUndoEnabled sur false.

Utilisez TextBox.AppendText(someValue) au lieu de TextBox.Text += someValue . Il est facile de rater puisque c’est sur TextBox, pas TextBox.Text. Comme SsortingngBuilder, cela évitera de créer des copies du texte entier chaque fois que vous ajoutez quelque chose.

Il serait intéressant de voir comment cela se compare à l’indicateur IsUndoEnabled de la réponse de keyboardP.

Ne pas append directement à la propriété text. Utilisez un object SsortingngBuilder pour l’ajout, puis, une fois terminé, définissez le texte sur la chaîne terminée à partir du constructeur de chaînes.

Au lieu d’utiliser une zone de texte, je procéderais comme suit:

  1. Ouvrez un fichier texte et diffusez les erreurs dans un fichier journal au cas où.
  2. Utilisez un contrôle de zone de liste pour représenter les erreurs afin d’éviter de copier des chaînes potentiellement massives.

Personnellement, j’utilise toujours ssortingng.Concat *. Je me souviens d’avoir lu une question sur Stack Overflow il y a des années, qui contenait des statistiques de profilage comparant les méthodes couramment utilisées et (semble) rappeler cette ssortingng.Concat .

Néanmoins, le mieux que je puisse trouver est cette question de référence et cette question spécifique Ssortingng.Format vs. SsortingngBuilder , qui mentionne que Ssortingng.Format utilise un SsortingngBuilder interne. Cela me fait me demander si votre cochon de mémoire se trouve ailleurs.

** En me basant sur le commentaire de James, je devrais mentionner que je ne fais jamais de formatage lourd, car je me concentre sur le développement basé sur le Web. *

Peut-être reconsidérer la TextBox? Une liste de blocage contenant des éléments sera probablement mieux exécutée.

Mais le principal problème semble être la configuration requirejse: l’affichage de 180 000 éléments ne peut pas viser un utilisateur (humain), ni le modifier en “temps réel”.

Le moyen préférable serait de montrer un échantillon des données ou un indicateur de progression.

Lorsque vous souhaitez le vider au niveau du mauvais utilisateur, mettez à jour les chaînes de lot. Aucun utilisateur ne pouvait afficher plus de 2 ou 3 changements par seconde. Donc, si vous produisez 100 / seconde, faites des groupes de 50.

Certaines réponses y ont fait allusion, mais personne n’a carrément dit ce qui est surprenant. Les chaînes sont immuables, ce qui signifie qu’une chaîne ne peut pas être modifiée après sa création. Par conséquent, chaque fois que vous concaténez à une chaîne existante, un nouvel object Ssortingng doit être créé. La mémoire associée à cet object Ssortingng doit évidemment être créée, ce qui peut coûter cher à mesure que vos chaînes deviennent de plus en plus grandes. A l’université, j’ai commis une erreur d’amateur: concaténer Ssortingngs dans un programme Java qui comprenait la compression Huffman. Lorsque vous concaténez de très grandes quantités de texte, la concaténation de chaînes peut vraiment vous nuire lorsque vous avez simplement utilisé SsortingngBuilder, comme certains l’ont mentionné.

Utilisez le SsortingngBuilder comme suggéré. Essayez d’estimer la taille de la chaîne finale, puis utilisez ce nombre lors de l’instanciation de SsortingngBuilder. SsortingngBuilder sb = new SsortingngBuilder (estSize);

Lors de la mise à jour de TextBox, utilisez simplement l’affectation, par exemple: textbox.text = sb.ToSsortingng ();

Surveillez les opérations croisées comme ci-dessus. Cependant, utilisez BeginInvoke. Pas besoin de bloquer le thread d’arrière-plan pendant que l’interface utilisateur se met à jour.

A) Intro: déjà mentionné, utilisez SsortingngBuilder

B) Point: ne pas mettre à jour trop souvent, c.-à-d.

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

C) Si c’est un travail ponctuel, utilisez l’architecture x64 pour restr dans la limite de 2 Go.

SsortingngBuilder dans ViewModel évitera les ViewModel chaînes et les liera à MyTextBox.Text . Ce scénario augmentera les performances plusieurs fois et diminuera l’utilisation de la mémoire.

Quelque chose qui n’a pas été mentionné, c’est que même si vous effectuez l’opération dans le thread d’arrière-plan, la mise à jour de l’élément d’interface utilisateur doit se produire sur le thread principal lui-même (dans WinForms de toute façon).

Lorsque vous mettez à jour votre zone de texte, avez-vous un code qui ressemble à

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

Si tel est le cas, votre mise à jour de l’interface utilisateur est certainement un goulot d’étranglement.

Je suggérerais que votre opération en arrière-plan utilise SsortingngBuilder comme indiqué ci-dessus, mais au lieu de mettre à jour la zone de texte à chaque cycle, essayez de la mettre à jour à intervalles réguliers pour voir si elle améliore les performances.

NOTE D’EDIT: n’ont pas utilisé WPF.

Vous dites que la mémoire croît de manière exponentielle. Non, c’est une croissance quadratique , c’est-à-dire une croissance polynomiale, qui n’est pas aussi spectaculaire qu’une croissance exponentielle.

Vous créez des chaînes contenant le nombre d’éléments suivant:

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

Avec n = 180,000 vous obtenez l’allocation totale de mémoire pour 16,200,090,000 items , soit 16.2 billion items ! Cette mémoire ne sera pas allouée à la fois, mais c’est beaucoup de travail de nettoyage pour le GC (garbage collector)!

Rappelez-vous également que la chaîne précédente (qui grossit) doit être copiée dans la nouvelle chaîne 179 999 fois. Le nombre total d’octets copiés va aussi avec n^2 !

Comme d’autres l’ont suggéré, utilisez plutôt un ListBox. Ici, vous pouvez append de nouvelles chaînes sans créer de chaîne énorme. Un SsortingngBuild n’aide pas, puisque vous souhaitez également afficher les résultats intermédiaires.