c# - 自动滚动文本框使用的内存超出预期

标签 c# .net 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);
        }
    }
}

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

更奇怪的是使用:

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

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

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

编辑:

我收到的前几个回复与如何跟踪内存泄漏有关,所以我想我应该分享我的方法。我结合使用 WinDbg 和 perfmon 来跟踪一段时间内的内存使用情况(从几个小时到几天)。所有 CLR 堆上的总字节数没有增加超过我的预期,但是随着更多消息的记录,私有(private)字节的总数稳步增加。这降低了 WinDbg 的用处,因为它的工具 (sos) 和命令(dumpheap、gcroot 等)基于 .NET 的托管内存。

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

最佳答案

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

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

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


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

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

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

关于c# - 自动滚动文本框使用的内存超出预期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1743448/

相关文章:

.net - 关于用 Entity Framework 替换企业库数据访问 block 的建议

c++ - 为什么我使用 HeapMemView 找不到分配在堆上的内存?

python - 为什么 Python 的 mmap 不能处理大文件?

c# - 如何列出 GC finalization 列表中的所有对象?

c# - 计算代码库中的匿名类?

c# - 检查异步事件是否仅引发一次

c# - 是否可以使用 LINQ 的 Skip 方法忽略列表项,然后在列表上应用跳过而不丢失该项目?

c# - 如何在 XAML 中设置 DataGrid 左上角的样式?

c# - 有没有办法用 EF Core 映射复杂类型

Python MemoryError 何时不是真的内存不足?