ループ中にTextBox.Textに追加すると、反復ごとにより多くのメモリが消費されるのはなぜですか?


82

短い質問

180,000回実行されるループがあります。各反復の終わりに、リアルタイムで更新されるTextBoxに結果を追加することになっています。

を使用するMyTextBox.Text += someValueと、アプリケーションが大量のメモリを消費し、数千レコード後に使用可能なメモリが不足します。

TextBox.Text180,000回にテキストを追加するより効率的な方法はありますか?

編集私はこの特定のケースの結果を本当に気にしませんが、なぜこれがメモリを大量に消費しているように見えるのか、そしてテキストボックスにテキストを追加するより効率的な方法があるかどうかを知りたいです。


長い(元の)質問

CSVファイルのID番号のリストを読み取り、それぞれのPDFレポートを生成する小さなアプリがあります。各pdfファイルが生成された後、ResultsTextBox.Text処理され、正常に処理されたレポートのID番号が追加されます。プロセスはバックグラウンドスレッドで実行されるため、アイテムが処理されると、ResultsTextBoxがリアルタイムで更新されます。

現在、180,000のID番号に対してアプリを実行していますが、アプリケーションが使用しているメモリは、時間の経過とともに指数関数的に増加しています。開始時は約90Kですが、約3000レコードで約250 MBを占有し、4000レコードでアプリケーションが約500MBのメモリを占有します。

Results TextBoxの更新をコメントアウトすると、メモリは約90Kで比較的静止したままなので、書き込みResultsText.Text += someValueがメモリを消費する原因であると推測できます。

私の質問は、これはなぜですか?メモリを消費しないTextBox.Textにデータを追加するためのより良い方法は何ですか?

私のコードは次のようになります:

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 });
}

また、このアプリは1回限りのものであり、すべてのレポートを生成するのに数時間(または数日:))かかることは問題ではありません。私の主な懸念は、システムのメモリ制限に達すると、実行が停止することです。

このことを実行するために、Results TextBoxを更新する行を残しておいても問題ありませんがTextBox.Text、将来のプロジェクトのためにデータを追加するためのよりメモリ効率の高い方法があるかどうかを知りたいです。


7
を使用しStringBuilderてテキストを追加し、完了したら、StringBuilder値をテキストボックスに割り当ててみてください。
keyboardP

1
それが何かを変えるかどうかはわかりませんが、新しいIDを追加するStringBuilderがあり、文字列ビルダーの新しい値で更新されるプロパティを使用して、これをtextbox.textにバインドするとどうなりますか?プロパティ。
BigL 2012年

2
string.Formatを呼び出すときにオブジェクト配列を初期化するのはなぜですか?配列の作成を回避できるように、2つのパラメーターを受け取るオーバーロードがあります。さらに、paramsオーバーロードを使用すると、舞台裏で配列が作成されます。
ChaosPandion 2012年

1
文字列連結は必ずしも非効率的ではありません。複数の作業単位にまたがって文字列を連結し、各作業単位の間に結果を表示する場合は、StringBuilderよりも効率的です。StringBuilderは、ループを介して文字列を作成し、ループの最後で結果を書き出す場合にのみ、実際に効率的です。
James Michael Hare 2012年

3
私が言うつもりだった、それは非常に印象的なマシンです:-)
James Michael Hare

回答:


119

メモリ使用量が非常に多い理由は、テキストボックスがスタックを維持しているため、ユーザーがテキストを元に戻したりやり直したりできるためだと思います。あなたの場合、その機能は必要ないようですのでIsUndoEnabled、falseに設定してみてください。


1
MSDNリンクから:「メモリリークコードから値を頻繁に設定するためにアプリケーションでメモリが増加している場合、テキストブロックの元に戻すスタックがメモリの「リーク」になる可能性があります。このプロパティを使用すると、無効にできます。それとメモリリークへの道を切り開く。」
ウェーブ

33
ほとんどの場合、ユーザーと開発者は、テキストボックスが標準のテキストボックスのように機能することを期待します(つまり、元に戻す/やり直す機能を備えています)。OPの要件などのエッジケースでは、それが障害になる可能性があります。大多数の人がそれを使用する場合、それはデフォルトであるはずです。エッジケースが標準機能を強制的にオプトインにすることを期待するのはなぜですか?
keyboardP

1
または、をUndoLimit現実的な値に設定することもできます。デフォルトの-1は、無制限のスタックを示します。ゼロ(0)も元に戻すを無効にします。
myermian 2012

14

TextBox.AppendText(someValue)代わりに使用してくださいTextBox.Text += someValue。TextBox.TextではなくTextBoxにあるため、見逃しがちです。StringBuilderと同様に、これにより、何かを追加するたびにテキスト全体のコピーが作成されるのを回避できます。

これがIsUndoEnabledkeyboardPの回答のフラグとどのように比較されるかを見るのは興味深いでしょう。


Windowsフォームの場合、WindowsフォームにはTextBox.IsUndoEnabledがないため、これが最善の解決策です
BrDaHa 2013

Winフォームでは、bool CanUndoプロパティがあります
imlokesh 2015

9

textプロパティに直接追加しないでください。追加にStringBuilderを使用し、完了したら、.textをstringbuilderからの完成した文字列に設定します


2
ループがバックグラウンドスレッドで実行され、結果がリアルタイムで更新されることを忘れました
レイチェル

5

テキストボックスを使用する代わりに、次のようにします。

  1. 万が一に備えて、テキストファイルを開き、エラーをログファイルにストリーミングします。
  2. リストボックスコントロールを使用してエラーを表し、大量の可能性のある文字列のコピーを回避します。

4

個人的にはいつもstring.Concat*を使っています。何年も前にStackOverflowで、一般的に使用されている方法を比較するプロファイリング統計があった質問を読んだことを覚えていstring.Concatます。

それにもかかわらず、私が見つけることができる最高のものは、この参照質問と、内部で使用することについて言及しているこの特定のString.FormatStringBuilder質問です。これはあなたの記憶の独り占めが他の場所にあるのだろうかと私に思わせます。String.FormatStringBuilder

** Jamesのコメントに基づいて、私はWebベースの開発に重点を置いているため、重い文字列のフォーマットを行うことは決してありません。*


私は同意します、時々人々は「常にXを使用するcuzXが最善である」と言う轍に陥ります。これは通常過度に単純化されています。string.Concat()、string.Format()、StringBuilderの間には微妙な点がたくさんあります。私の経験則は、それが意図されているものをそれぞれ使用することです(それはばかげているように聞こえますが、私は知っていますが、それは当てはまります)。文字列を結合するとき(そして結果をすぐに使用するとき)はconcatを使用し、重要な文字列形式(パディング、数値形式など)を実行するときはFormatを使用し、ループ中に文字列を構築するためにStringBuilderを使用します。ループの最後に使用されます。
James Michael Hare 2012年

@JamesMichaelHare、それは私には理にかなっています。ここではstring.Format/の使用StringBuilderがより適切であることを示唆していますか?
jwheron 2012年

いいえ、私は、concatは通常単純な文字列concatに最適であるというあなたの一般的な点に同意していました。「経験則」の問題は、BCLが変更された場合に、.NETバージョンからバージョンに変更される可能性があることです。したがって、論理的に正しい構成に固執する方が保守性が高く、通常はそのタスクのパフォーマンスが向上します。私は実際にここで3つを比較した古いブログ投稿を持っていました:geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/…–
James Michael Hare

十分に注意してください-確かにしたかっただけです-そして「常に」という言葉の私の使用を修飾するために編集された答え。
jwheron 2012年

3

たぶんTextBoxを再考しますか?文字列Itemsを保持するListBoxは、おそらくパフォーマンスが向上します。

しかし、主な問題は要件のようです。180,000のアイテムを表示することは、(人間の)ユーザーを対象にすることはできず、「リアルタイム」で変更することもできません。

望ましい方法は、データのサンプルまたは進行状況インジケーターを表示することです。

貧弱なユーザーにダンプしたい場合は、バッチ文字列を更新します。ユーザーは、1秒あたり2つまたは3つを超える変更を識別できませんでした。したがって、100 /秒を生成する場合は、50のグループを作成します。


ヘンクに感謝します。これは一度きりのことだったので、書くのが面倒でした。ステータスが何であるかを知るために、ある種の視覚出力が必要でした。また、テキスト選択機能とScrollBarが必要でした。ScrollViewer / Labelを使用できたと思いますが、TextBoxにはScrollBarrsが組み込まれています。問題が発生するとは思っていませんでした:)
Rachel

2

いくつかの反応はそれをほのめかしました、しかし誰もそれをはっきりと述べていません、それは驚くべきことです。文字列は不変です。つまり、文字列は作成後に変更できません。したがって、既存の文字列に連結するたびに、新しい文字列オブジェクトを作成する必要があります。その文字列オブジェクトに関連付けられたメモリも明らかに作成する必要があります。これは、文字列がどんどん大きくなるにつれて高価になる可能性があります。大学では、ハフマン符号化圧縮を行うJavaプログラムで文字列を連結するというアマチュアの間違いを犯したことがあります。非常に大量のテキストを連結している場合、ここで言及しているように、StringBuilderを単純に使用できたとしても、文字列の連結は非常に害を及ぼす可能性があります。


2

提案されているようにStringBuilderを使用します。最終的な文字列サイズを見積もり、StringBuilderをインスタンス化するときにその数値を使用してみてください。StringBuilder sb = new StringBuilder(estSize);

TextBoxを更新するときは、割り当てを使用してください。例:textbox.text = sb.ToString();

上記のようなクロススレッド操作に注意してください。ただし、BeginInvokeを使用してください。UIの更新中にバックグラウンドスレッドをブロックする必要はありません。


1

A)イントロ:すでに述べたように、使用する StringBuilder

B)ポイント:頻繁に更新しないでください。

DateTime dtLastUpdate = DateTime.MinValue;

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

C)それが1回限りのジョブである場合は、x64アーキテクチャを使用して2Gbの制限内にとどまります。


1

StringBuilderinViewModelは、文字列の再バインドの混乱を回避し、にバインドしMyTextBox.Textます。このシナリオでは、パフォーマンスが何倍も向上し、メモリ使用量が減少します。


0

言及されていないことは、バックグラウンドスレッドで操作を実行している場合でも、UI要素自体の更新はメインスレッド自体で行われる必要があるということです(とにかくWinFormsで)。

テキストボックスを更新するときに、次のようなコードはありますか

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

もしそうなら、あなたのバックグラウンド操作は間違いなくUIアップデートによってボトルネックになっています。

上記のようにバックグラウンド操作でStringBuilderを使用することをお勧めしますが、テキストボックスをサイクルごとに更新する代わりに、定期的に更新して、パフォーマンスが向上するかどうかを確認してください。

編集メモ:WPFを使用していません。


0

あなたは記憶が指数関数的に成長すると言います。いいえ、それは二次成長です、つまり多項式成長であり、指数関数的成長ほど劇的ではありません。

次の数のアイテムを保持する文字列を作成しています。

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

n = 180,000、あなたの総メモリ割り当てを取得するために16,200,090,000 items、すなわち16.2 billion items!このメモリは一度に割り当てられませんが、GC(ガベージコレクター)のクリーンアップ作業はたくさんあります!

また、前の文字列(成長している)を新しい文字列に179,999回コピーする必要があることに注意してください。コピーされたバイトの総数はn^2も同様です!

他の人が示唆しているように、代わりにリストボックスを使用してください。ここでは、巨大な文字列を作成せずに新しい文字列を追加できます。StringBuild中間結果も表示したいので、Aは役に立ちません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.