У меня есть приложение, которое регистрирует сообщения на экране с помощью 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 имеется ~ 1 МБ сообщений, приложение может потреблять до 6 МБ памяти (в дополнение к тому, что оно использует, когда для параметра logToScreen установлено значение false). Увеличение всегда как минимум вдвое больше, чем я ожидаю, и (как в моем примере) иногда больше.
Что более странно, так это использование:
this.txtBoxLogging.Clear();
for (int i = 0; i < 3; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Не освобождает память (на самом деле она немного увеличивается).
Любая идея, по которой происходит память, когда я регистрирую эти сообщения? Я не верю, что это имеет какое-либо отношение к вызовам Win32, но я включил его в тщательное описание.
EDIT:
Первые две ответы, которые я получил, были связаны с тем, как отслеживать утечку памяти, поэтому я решил поделиться своей методологией. Я использовал комбинацию WinDbg и perfmon для отслеживания использования памяти с течением времени (от пары часов до нескольких дней). Общее количество байтов во всех кучах CLR не увеличивается больше, чем я ожидаю, но общее количество частных байтов постоянно увеличивается по мере регистрации большего количества сообщений. Это делает WinDbg менее полезным, так как его инструменты (sos) и команды (dumpheap, gcroot и т.д.) Основаны на управляемой памяти .NET.
Вероятно, почему GC.Collect() не может мне помочь, поскольку он ищет свободную память в куче CLR. Моя утечка находится в неконтролируемой памяти.