2012-01-04 21 views
81

短缺問題爲什麼一個循環過程中追加到TextBox.Text佔用更多的內存每次迭代?

我有一個運行18萬次的循環。在每次迭代結束時,它應該將結果附加到實時更新的TextBox。

使用MyTextBox.Text += someValue導致吃大量內存的應用程序,它經過幾千年的記錄耗盡可用內存。

是否有附加文本到TextBox.Text 18萬次的更有效的方法?

編輯我真的不關心這種特殊情況下的結果,但是我想知道爲什麼這似乎是一個內存豬,如果有將文本追加到一個TextBox更有效的方式。


龍(原件)問題

我有一個小的應用程序,它讀取一個CSV文件ID號的列表,併產生爲每一個PDF報告。生成的每個pdf文件後,ResultsTextBox.Text被附加一個得到處理報表的ID號,並且它被成功處理。 過程在後臺線程上運行,所以ResultsTextBox得到更新項目得到處理

我目前正在運行鍼對18萬ID號的應用,但是應用程序內存佔用成倍增長,隨着時間的實時過去了。它首先90K左右,而是由約3000條記錄它佔用了大約250MB和4000記錄應用程序佔用大約500 MB的內存。

如果我註釋掉結果文本框的更新,內存在大約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 }); 
} 

這也應該是值得一提的是,應用程序是一個一次性的事情,沒關係,這是要花費幾個小時(或幾天:))來生成所有的報告。我主要關心的是,如果它達到系統內存限制,它將停止運行。

我很好,離開行更新結果文本框註釋掉運行這個東西,但我想知道是否有更多的內存有效的方式將數據追加到TextBox.Text爲未來的項目。

+7

你可以嘗試使用'StringBuilder'來追加文本,然後在完成時將'StringBuilder'值賦給文本框。 – keyboardP 2012-01-04 20:59:02

+0

@keyboardP我忘了提到在後臺線程上運行的循環,並且結果文本框實時更新 – Rachel 2012-01-04 21:01:06

+1

我不知道它是否會改變任何事情,但如果你有一個StringBuilder會附加新的Id-s並且您將使用一個屬性,該屬性使用字符串構建器的新值進行更新,並將其綁定到textbox.text屬性。 – BigL 2012-01-04 21:01:37

回答

119

我懷疑內存使用量如此之大的原因是因爲文本框保留了一個堆棧,以便用戶可以撤消/重做文本。您的情況似乎不需要該功能,因此請嘗試將IsUndoEnabled設置爲false。

+1

從MSDN鏈接:「內存泄漏 如果你有一個內存增加在您的應用程序中,因爲您經常從代碼中設置值,所以textblock的撤消堆棧可能是內存的「泄漏」。通過使用此屬性,您可以禁用它並清除泄漏內存的方式。 – wave 2012-01-04 22:00:55

+33

大多數時候,用戶和開發人員都希望文本框能像標準文本框一樣工作(即可以撤銷/重做)。在諸如OP的要求等邊緣情況下,它可能被證明是一種障礙。如果大多數人使用它,那麼它應該是默認的。爲什麼你會期望一個邊緣案例強制標準功能成爲選擇? – keyboardP 2012-01-04 22:22:13

+1

或者,您還可以將'UndoLimit'設置爲實際值。默認值-1表示無限制堆棧。零(0)也將禁用撤消。 – 2012-07-18 14:35:13

9

不要直接附加到文本屬性。使用StringBuilder進行追加,然後完成後,將.text設置爲字符串構建器中的完成字符串

+2

我忘了提及循環運行在後臺線程和結果得到實時更新 – Rachel 2012-01-04 21:01:26

5

而不是使用文本框的我會做到以下幾點:

  1. 打開一個文本文件,以防萬一流的錯誤日誌文件。
  2. 使用列表框控件來表示錯誤以避免複製潛在的大量字符串。
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)如果這是一次性的工作,使用x64體系結構保持在2Gb的限度內。

4

就我個人而言,我總是使用string.Concat *。我記得在幾年前的Stack Overflow上讀到一個問題,它有比較常用方法的分析統計,並且(似乎)回想起string.Concat贏了。

儘管如此,我能找到的最好是this reference question這個特定String.Format vs. StringBuilder問題,其中提到,String.Format使用StringBuilder內部。這讓我懷疑你的記憶豬是否在別處。

**基於詹姆斯的評論,我要指出,我從來不做重字符串格式化,因爲我專注於基於網絡的發展。*

+0

我同意,有時人們會說「總是使用X cuz X是最好的」,這通常是過分簡單化。 string.Concat(),string.Format()和StringBuilder之間有很多細微之處。我的經驗法則是使用它的目的(聽起來很愚蠢,我知道,但它是真實的)。我在連接字符串(然後立即使用結果)時使用concat,當我執行非重要的字符串格式(填充,數字格式等)時使用Format,以及用於在循環期間構建字符串的StringBuilder在循環結束時使用。 – 2012-01-04 21:12:47

+0

@JamesMichaelHare,這對我有意義;你在暗示使用'string.Format' /'StringBuilder'更合適嗎? – jwiscarson 2012-01-04 21:14:54

+0

哦,不,我只是同意你的一般觀點,即concat通常最適合簡單字符串連接。 「經驗法則」的問題在於,如果BCL發生變化,它們可以從.NET版本更改爲版本,因此堅持邏輯正確的構造更易於維護,並且通常對其任務執行得更好。我其實有一個較早的博客文章,我在這裏比較了三個:http://geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/c-string-compares-and-concatenations.aspx – 2012-01-04 21:17:43

3

可能重新考慮這個文本框?包含字符串Items的ListBox可能會更好。

但主要問題似乎是要求,顯示180,000項不能針對(人類)用戶,也不能在「實時」中進行更改。

最好的方法是顯示數據樣本或進度指示器。

當你確實想把它轉儲到可憐的用戶時,批量字符串更新。沒有用戶每秒可以看到2或3次以上的變化。因此,如果您生產100個/秒,則使羣組數量爲50.

+0

謝謝Henk。這是一次性的事情,所以在寫作時我很懶。我想要一些視覺輸出來知道狀態是什麼,我想要文本選擇功能和ScrollBar。我想我可以使用ScrollViewer/Label,但TextBoxes內置ScrollBarrs。我沒想到它會導致問題:) – Rachel 2012-01-04 21:20:37

1

StringBuilder in ViewModel將避免字符串重新綁定混亂並將其綁定到MyTextBox.Text。這種情況會多次提高性能,並減少內存使用量。

2

一些反應已經提到它,但沒有人直接說出這是令人驚訝的。 字符串是不可變的,這意味着字符串在創建後無法修改。因此,每次連接到一個現有的String時,都需要創建一個新的String對象。與該String對象關聯的內存顯然也需要創建,隨着您的Strings變得越來越大,這可能會變得昂貴。在大學裏,我曾經在做過霍夫曼編碼壓縮的Java程序中犯下了連接字符串的業餘錯誤。當你連接了大量的文本時,當你可以簡單地使用StringBuilder時,字符串連接會真正地傷害你,正如這裏提到的一些。

0

未提及的一點是,即使您在後臺線程中執行操作,UI元素本身的更新也必須在主線程本身(無論如何都在WinForms中)發生。

更新您的文本框,你有看起來像

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

如果是這樣,那麼你的後臺運算肯定是由UI更新瓶頸的任何代碼。

我建議你的背景運算使用StringBuilder如上所述,但不是更新文本框每個週期,嘗試定期更新它,看它是否提高性能爲您服務。

編輯注:有沒有使用過WPF。

2

按照建議使用StringBuilder。 嘗試估算最終的字符串大小,然後在實例化StringBuilder時使用該數字。 StringBuilder sb = new StringBuilder(estSize);

當更新文本框只是用分配例如:textbox.text = sb.ToString();

觀察上述的跨線程操作。但是使用BeginInvoke。當UI更新時,不需要阻止 後臺線程。

14

使用TextBox.AppendText(someValue)代替TextBox.Text += someValue。它很容易錯過,因爲它在TextBox上,而不是TextBox.Text。像StringBuilder一樣,這將避免每次添加內容時都創建整個文本的副本。

,看看如何與此相比,IsUndoEnabled標誌從keyboardP的答案會很有趣。

+0

對於Windows窗體,這是最好的解決方案,因爲windows窗體沒有TextBox.IsUndoEnabled – BrDaHa 2013-07-31 17:13:50

+0

在Win窗體中,你有一個'bool CanUndo'屬性 – imlokesh 2015-04-12 20:34:35

0

你說記憶呈指數增長。不是,它是一個quadratic growth,即一個多項式增長,它不像指數增長那麼戲劇化。

你正在創建的字符串持有下列數量的項目:

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一起!

正如其他人所建議的,改用ListBox。在這裏,您可以添加新字符串而不創建大字符串。 A StringBuild不起作用,因爲您還想顯示中間結果。

相關問題