c# – 自动滚动文本框使用比预期更多的内存

我有一个应用程序使用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堆上寻找可用内存.我的泄漏似乎在未管理的内存中.

你如何确定内存使用情况?您将不得不观察应用程序的CLR内存使用情况,而不是系统为整个应用程序使用的内存(可以使用Perfmon).也许你已经在使用正确的监控方法.

在我看来,你在内部使用StringBuilder.如果是这样,那将会解释内存倍增,因为这是StringBuilder在内部工作的方式.

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

编辑:
我会离开上面,因为它可能仍然是真的,但我查看了AppendText的内部.它不附加(即,到StringBuilder),而是设置SelectedText属性,它不设置字符串,但发送一个Win32消息(字符串在检索时被缓存).

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

编辑2:不知道是否会有任何改变,但您可以考虑自己调用SendMessage.但是它确实开始看起来像您需要自己的滚动算法和您自己的所有者绘制的文本框以获取内存.

翻译自:https://stackoverflow.com/questions/1743448/auto-scrolling-text-box-uses-more-memory-than-expected

转载注明原文:c# – 自动滚动文本框使用比预期更多的内存