2009-11-16 52 views
12

我有一個使用TextBox將消息記錄到屏幕的應用程序。更新函數使用一些Win32函數來確保該框自動滾動到最後,除非用戶正在查看另一行。這裏是更新功能:自動滾動文本框使用比預期更多的內存

private bool logToScreen = true; 

// Constants for extern calls to various scrollbar functions 
private const int SB_HORZ = 0x0; 
private const int SB_VERT = 0x1; 
private const int WM_HSCROLL = 0x114; 
private const int WM_VSCROLL = 0x115; 
private const int SB_THUMBPOSITION = 4; 
private const int SB_BOTTOM = 7; 
private const int SB_OFFSET = 13; 

[DllImport("user32.dll")] 
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw); 
[DllImport("user32.dll", CharSet = CharSet.Auto)] 
private static extern int GetScrollPos(IntPtr hWnd, int nBar); 
[DllImport("user32.dll")] 
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam); 
[DllImport("user32.dll")] 
static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos); 

private void LogMessages(string text) 
{ 
    if (this.logToScreen) 
    { 
     bool bottomFlag = false; 
     int VSmin; 
     int VSmax; 
     int sbOffset; 
     int savedVpos; 
     // Make sure this is done in the UI thread 
     if (this.txtBoxLogging.InvokeRequired) 
     { 
      this.txtBoxLogging.Invoke(new TextBoxLoggerDelegate(LogMessages), new object[] { text }); 
     } 
     else 
     { 
      // Win32 magic to keep the textbox scrolling to the newest append to the textbox unless 
      // the user has moved the scrollbox up 
      sbOffset = (int)((this.txtBoxLogging.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight)/(this.txtBoxLogging.Font.Height)); 
      savedVpos = GetScrollPos(this.txtBoxLogging.Handle, SB_VERT); 
      GetScrollRange(this.txtBoxLogging.Handle, SB_VERT, out VSmin, out VSmax); 
      if (savedVpos >= (VSmax - sbOffset - 1)) 
       bottomFlag = true; 
      this.txtBoxLogging.AppendText(text + Environment.NewLine); 
      if (bottomFlag) 
      { 
       GetScrollRange(this.txtBoxLogging.Handle, SB_VERT, out VSmin, out VSmax); 
       savedVpos = VSmax - sbOffset; 
       bottomFlag = false; 
      } 
      SetScrollPos(this.txtBoxLogging.Handle, SB_VERT, savedVpos, true); 
      PostMessageA(this.txtBoxLogging.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0); 
     } 
    } 
} 

現在奇怪的是,文本框消耗至少是我期望的內存的兩倍。例如,當TextBox中有大約1MB的消息時,應用程序可以消耗多達6MB的內存(除了將logToScreen設置爲false時使用的內容之外)。這個增長總是至少是我期望的兩倍,並且(如我的例子)有時更多。

什麼是更奇怪的是,使用:

this.txtBoxLogging.Clear(); 
for (int i = 0; i < 3; i++) 
{ 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
} 

不釋放內存(事實上,它略有增加)。

任何想法在記錄這些消息時記憶正在發生?我不相信它與Win32調用有任何關係,但我已經將它包括在內了。

編輯:

我得到的第一對夫婦的反應是涉及到如何跟蹤內存泄漏,所以我想我應該分享我的方法。我使用了WinDbg和perfmon的組合來隨着時間的推移(從幾個小時到幾天)跟蹤內存的使用情況。所有CLR堆的總字節數並沒有增加超過我的預期,但隨着更多的消息被記錄,專用字節的總數穩步增加。這使得WinDbg不太有用,因爲它的工具(sos)和命令(dumpheap,gcroot等)基於.NET的託管內存。

這可能是爲什麼GC.Collect()不能幫助我,因爲它只是在CLR堆上尋找空閒內存。我的泄漏似乎在未被管理的內存中。

+0

先生,你是我的救命恩人!最後,TextboxBase追加滾動鎖定工作! – 2010-02-28 14:48:56

+0

有趣的提示:使用richTextBox.Text + = str而不是richTextBox.AppendText(str)消除了閃爍,但是當字符串變大時導致大量減速 – 2010-03-01 09:07:41

回答

3

你如何確定內存使用情況?您必須觀察應用程序的CLR內存使用情況,而不是系統爲整個應用程序使用的內存(您可以使用Perfmon)。也許你已經在使用正確的監控方法了。

在我看來,你在內部使用StringBuilder。如果是這樣,那將解釋內存的翻倍,因爲這是StringBuilder內部工作的方式。

如果您的對象的引用仍在範圍內,或者您的任何代碼使用了靜態變量,則GC.Collect()可能不會執行任何操作。


編輯:
我將離開之上,因爲它可能仍然是事實,但我擡頭AppendText的內部。它不追加(即對StringBuilder),而是設置SelectedText屬性,該屬性不設置字符串,但發送Win32消息(在檢索時緩存字符串)。

因爲字符串是不可變的,所以對於每個字符串,都會有三個副本:一個在調用應用程序中,一個在基址Control的「緩存」中,另一個在實際的Win32文本框控件中。每個字符都是兩個字節寬。這意味着任何1MB的文本將消耗6MB的內存(我知道,這有點簡單,但基本上看起來發生了什麼)。

編輯2:不知道它是否會做出任何改變,但你可以考慮自己調用SendMessage。但它肯定開始看起來像你需要自己的滾動算法和你自己的所有者繪製的文本框來減少內存。

+0

內存調試使用了permon和WinDbg的組合。內存不會出現在CLR堆上,它在非託管內存中的某處。儘管如此,這是違規的代碼。如果我將logToScreen設置爲false,則內存使用不會隨着時間(天)而增加。 – cgyDeveloper 2009-11-16 17:09:49

+0

感謝您的更新,對此問題有所瞭解。實際上,P/Invoke SendMessage用於發送字符串(在設置時不會內部存儲!)。然後,編組和win32 API是剩餘可能的內存食用者。 Iirc,編組不會複製字符串,但我可能會誤解。 – Abel 2009-11-16 17:25:10

+0

有趣的是,你在哪裏找到關於AppendText內部的信息?我一直在MSDN上,但沒有找到任何深入的內容。這很可能是我正在尋找的。 – cgyDeveloper 2009-11-16 17:30:14

1

跟蹤應用程序使用的內存量,特別是垃圾回收語言,是一件棘手的事情。人們經常使用應用程序的總內存數來確定仍在使用的對象(例如通過任務管理器)。這對本地應用程序來說是有效的,但會給託管應用程序帶來非常令人誤解的結果。

爲了正確確定CLR對象所使用的內存量,您需要使用專門用於測量的內存量。例如,我發現最好的方法是使用WinDbg和sos.dll的組合來測量當前的根目標對象。這將告訴你管理對象的大小,並指出實際佔用額外內存的對象。

這裏是一個很好的文章關於這個問題

+0

是的,這是一篇非常好的文章。使用WinDbg和perfmon的組合,我確定額外的內存不在CLR託管內存中(使得WinDbg的用處更小),但是在非託管內存中。 – cgyDeveloper 2009-11-16 17:21:20

+0

On *「我發現最好的方式是使用WinDbg和sos.dll的組合」*:有趣,但是使用Perfmon和適用的CLR計數器不是一樣容易嗎?許多(不是全部)與CLR + GC相關的內存問題可以通過這種方式進行追蹤。 – Abel 2009-11-16 17:22:11

+0

Perfmon是最好的開始,然後WinDbg可以告訴你爲什麼某個對象不符合垃圾回收的條件(使用sos.dll的gcroot命令)。 – cgyDeveloper 2009-11-16 17:32:43