自动滚动文本框使用的内存比预期的多

cgy*_*per 15 .net c# memory textbox

我有一个应用程序使用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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在奇怪的是文本框消耗的内存至少是我期望的内存的两倍.例如,当TextBox中有大约1MB的消息时,应用程序最多可以消耗6MB的内存(除了当logToScreen设置为false时使用的内容).增长总是至少是我预期的两倍,并且(如我的例子中)有时更多.

更奇怪的是使用:

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

不释放内存(事实上,它略有增加).

当我记录这些消息时,知道内存的去向吗?我不相信它与Win32调用有任何关系,但我把它包括在内是彻底的.

编辑:

我得到的前几个响应与如何跟踪内存泄漏有关,所以我认为我应该分享我的方法.我使用WinDbg和perfmon的组合来跟踪内存使用情况(从几个小时到几天).所有CLR堆上的总字节数不会增加超过我的预期,但随着更多消息的记录,私有字节总数会稳定增加.这使得WinDbg的用处不大,因为它是工具(SOS)和命令(dumpheap,gcroot等)都是基于.NET的托管内存.

这可能是GC.Collect()无法帮助我的原因,因为它只是在CLR堆上寻找空闲内存.我的泄漏似乎是在未管理的内存中.

Abe*_*bel 3

您如何确定内存使用情况?您必须查看应用程序的 CLR 内存使用情况,而不是系统为整个应用程序使用的内存(您可以使用 Perfmon 来实现)。也许您已经在使用正确的监控方法。

在我看来,你正在StringBuilder内部使用。如果是这样,那就可以解释内存加倍的原因,因为这就是 StringBuilder 内部工作的方式。

GC.Collect()如果对对象的引用仍在范围内,或者任何代码使用静态变量,则可能不会执行任何操作。


编辑:
我将保留上面的内容,因为它可能仍然是正确的,但我查看了AppendText内部结构。它不附加(即,到 StringBuilder),而是设置属性SelectedText,该属性不设置字符串,而是发送 Win32 消息(字符串在检索时缓存)。

因为字符串是不可变的,这意味着对于每个字符串,将有三份副本:一份在调用应用程序中,一份在基的“缓存”中Control,一份在实际的 Win32 文本框控件中。每个字符有两个字节宽。这意味着任何 1MB 的文本都会消耗 6MB 的内存(我知道,这有点简单化,但这基本上就是所发生的情况)。

编辑2:不确定是否会做出任何改变,但你可以考虑给SendMessage自己打电话。但看起来您确实需要自己的滚动算法和自己绘制的文本框来减少内存。