Kurze Frage
Ich habe eine Schleife, die 180.000 Mal läuft. Am Ende jeder Iteration sollen die Ergebnisse an eine TextBox angehängt werden, die in Echtzeit aktualisiert wird.
Verwenden von MyTextBox.Text += someValue
führt dazu, dass die Anwendung sehr viel Speicher verbraucht und nach einigen tausend Datensätzen nicht mehr genügend Speicher verfügbar ist.
Gibt es eine effizientere Möglichkeit, Text an TextBox.Text
180.000 Mal anzuhängen?
Bearbeiten Das Ergebnis dieses speziellen Falls ist mir wirklich egal, aber ich möchte wissen, warum dies ein Speicherfresser zu sein scheint und ob es eine effizientere Möglichkeit gibt, Text an eine TextBox anzuhängen.
Lange (Original) Frage
Ich habe eine kleine App, die eine Liste von ID-Nummern in einer CSV-Datei liest und für jede einen PDF-Bericht generiert. Nachdem jede PDF-Datei generiert wurde, ResultsTextBox.Text
wird die ID-Nummer des Berichts angehängt, der verarbeitet wurde und der erfolgreich verarbeitet wurde. Der Prozess wird in einem Hintergrundthread ausgeführt, sodass die ResultsTextBox in Echtzeit aktualisiert wird, wenn Elemente verarbeitet werden
Ich führe die App derzeit mit 180.000 ID-Nummern aus, aber der Speicher, den die Anwendung belegt, wächst im Laufe der Zeit exponentiell. Es beginnt bei ungefähr 90 KB, aber bei ungefähr 3000 Datensätzen nimmt es ungefähr 250 MB ein und bei 4000 Datensätzen nimmt die Anwendung ungefähr 500 MB Speicher ein.
Wenn ich die Aktualisierung der Ergebnistextbox auskommentiere, bleibt der Speicher mit ungefähr 90 KB relativ stationär, sodass ich davon ausgehen kann, dass geschrieben wird ResultsText.Text += someValue
dazu führt, dass der Speicher belegt wird.
Meine Frage ist, warum ist das so? Was ist eine bessere Möglichkeit, Daten an eine TextBox.Text anzuhängen, die keinen Speicher verbraucht?
Mein Code sieht folgendermaßen aus:
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 });
}
Es sollte auch erwähnenswert sein, dass die App eine einmalige Sache ist und es keine Rolle spielt, dass es einige Stunden (oder Tage :) dauern wird, bis alle Berichte erstellt sind. Mein Hauptanliegen ist, dass es nicht mehr läuft, wenn es das Systemspeicherlimit erreicht.
Ich bin in Ordnung, wenn ich die Zeile verlasse, in der die Ergebnisse aktualisiert werden, die TextBox auskommentiert hat, um dieses Ding auszuführen, aber ich würde gerne wissen, ob es eine speichereffizientere Möglichkeit gibt, Daten TextBox.Text
für zukünftige Projekte an a anzuhängen .
StringBuilder
den Text mit a anzuhängen, und dann nach Abschluss denStringBuilder
Wert dem Textfeld zuweisen .Antworten:
Ich vermute, der Grund für die so große Speichernutzung liegt darin, dass Textfelder einen Stapel verwalten, damit der Benutzer Text rückgängig machen / wiederholen kann. Diese Funktion scheint in Ihrem Fall nicht erforderlich zu sein. Versuchen Sie daher, sie
IsUndoEnabled
auf false zu setzen.quelle
UndoLimit
einen realistischen Wert festlegen . Der Standardwert -1 gibt einen unbegrenzten Stapel an. Null (0) deaktiviert auch das Rückgängigmachen.Verwenden Sie
TextBox.AppendText(someValue)
anstelle vonTextBox.Text += someValue
. Es ist leicht zu übersehen, da es sich um TextBox handelt, nicht um TextBox.Text. Wie bei StringBuilder wird dadurch vermieden, dass jedes Mal, wenn Sie etwas hinzufügen, Kopien des gesamten Textes erstellt werden.Es wäre interessant zu sehen, wie dies mit dem
IsUndoEnabled
Flag aus der Antwort von keyboardP verglichen wird .quelle
bool CanUndo
EigenschaftHängen Sie nicht direkt an die Texteigenschaft an. Verwenden Sie einen StringBuilder für das Anhängen. Wenn dies erledigt ist, setzen Sie den Text aus dem Stringbuilder auf den fertigen String
quelle
Anstatt ein Textfeld zu verwenden, würde ich Folgendes tun:
quelle
Persönlich benutze ich immer
string.Concat
*. Ich erinnere mich, dass ich hier vor Jahren eine Frage zu Stack Overflow gelesen habe, in der Profilstatistiken zum Vergleich der häufig verwendeten Methoden verwendet wurden, und mich daran zu erinnern scheintstring.Concat
sie sich durchgesetzt haben.Das Beste, was ich finden kann, ist jedoch diese Referenzfrage und diese spezifische
String.Format
vs.StringBuilder
Frage, in der erwähnt wird, dassString.Format
einStringBuilder
intern verwendet wird. Ich frage mich, ob Ihr Erinnerungsschwein woanders liegt.** Basierend auf James 'Kommentar sollte ich erwähnen, dass ich niemals schwere Zeichenfolgen formatiere, da ich mich auf die webbasierte Entwicklung konzentriere. *
quelle
string.Format
/StringBuilder
hier angemessener ist?Vielleicht die TextBox überdenken? Eine ListBox mit String-Elementen wird wahrscheinlich eine bessere Leistung erzielen.
Das Hauptproblem scheinen jedoch die Anforderungen zu sein: Das Anzeigen von 180.000 Elementen kann nicht an einen (menschlichen) Benutzer gerichtet werden und wird auch nicht in "Echtzeit" geändert.
Der bevorzugte Weg wäre, eine Stichprobe der Daten oder eine Fortschrittsanzeige anzuzeigen.
Wenn Sie es auf dem armen Benutzer sichern möchten, werden die Stapelzeichenfolgen aktualisiert. Kein Benutzer konnte mehr als 2 oder 3 Änderungen pro Sekunde feststellen. Wenn Sie also 100 / Sekunde produzieren, bilden Sie Gruppen von 50.
quelle
Einige Antworten haben darauf angespielt, aber niemand hat es direkt gesagt, was überraschend ist. Strings sind unveränderlich, was bedeutet, dass ein String nach seiner Erstellung nicht mehr geändert werden kann. Daher muss jedes Mal, wenn Sie mit einem vorhandenen String verketten, ein neues String-Objekt erstellt werden. Der mit diesem String-Objekt verknüpfte Speicher muss natürlich auch erstellt werden, was teuer werden kann, wenn Ihre Strings immer größer werden. Im College habe ich einmal den Amateurfehler gemacht, Strings in einem Java-Programm zu verketten, das die Huffman-Codierungskomprimierung durchgeführt hat. Wenn Sie extrem große Textmengen verketten, kann die Verkettung von Zeichenfolgen Sie wirklich verletzen, wenn Sie einfach StringBuilder hätten verwenden können, wie einige hier erwähnt haben.
quelle
Verwenden Sie den StringBuilder wie vorgeschlagen. Versuchen Sie, die endgültige Zeichenfolgengröße zu schätzen, und verwenden Sie diese Zahl, wenn Sie den StringBuilder instanziieren. StringBuilder sb = neuer StringBuilder (estSize);
Verwenden Sie beim Aktualisieren der TextBox einfach die Zuweisung, z. B.: Textbox.text = sb.ToString ();
Achten Sie auf Cross-Thread-Operationen wie oben. Verwenden Sie jedoch BeginInvoke. Der Hintergrund-Thread muss nicht blockiert werden, während die Benutzeroberfläche aktualisiert wird.
quelle
A) Intro: bereits erwähnt, verwenden
StringBuilder
B) Punkt: Nicht zu häufig aktualisieren, dh
C) Wenn dies ein einmaliger Auftrag ist, verwenden Sie die x64-Architektur, um innerhalb des 2-GB-Grenzwerts zu bleiben.
quelle
StringBuilder
InViewModel
vermeidet das Durcheinander von String-Bindungen und bindet es anMyTextBox.Text
. Dieses Szenario erhöht die Leistung um ein Vielfaches und verringert die Speichernutzung.quelle
Was nicht erwähnt wurde, ist, dass selbst wenn Sie den Vorgang im Hintergrundthread ausführen, die Aktualisierung des UI-Elements selbst auf dem Hauptthread selbst erfolgen muss (in WinForms sowieso).
Haben Sie beim Aktualisieren Ihres Textfelds Code, der aussieht?
Wenn ja, dann wird Ihre Hintergrundoperation durch das UI-Update definitiv zum Engpass.
Ich würde vorschlagen, dass Ihr Hintergrundbetrieb StringBuilder wie oben erwähnt verwendet, aber anstatt das Textfeld in jedem Zyklus zu aktualisieren, versuchen Sie, es in regelmäßigen Abständen zu aktualisieren, um festzustellen, ob es die Leistung für Sie erhöht.
BEARBEITEN HINWEIS: WPF nicht verwendet.
quelle
Sie sagen, das Gedächtnis wächst exponentiell. Nein, es ist ein quadratisches Wachstum , dh ein Polynomwachstum, das nicht so dramatisch ist wie ein exponentielles Wachstum.
Sie erstellen Zeichenfolgen mit der folgenden Anzahl von Elementen:
Mit erhalten
n = 180,000
Sie die Gesamtspeicherzuordnung für16,200,090,000 items
, dh16.2 billion items
! Dieser Speicher wird nicht sofort zugewiesen, aber es ist eine Menge Aufräumarbeiten für den GC (Garbage Collector)!Beachten Sie außerdem, dass die vorherige Zeichenfolge (die wächst) 179.999 Mal in die neue Zeichenfolge kopiert werden muss. Die Gesamtzahl der kopierten Bytes geht mit
n^2
!Verwenden Sie stattdessen eine ListBox, wie andere vorgeschlagen haben. Hier können Sie neue Zeichenfolgen anhängen, ohne eine große Zeichenfolge zu erstellen. A
StringBuild
hilft nicht, da Sie auch die Zwischenergebnisse anzeigen möchten.quelle