问题描述
我有一个使用 TextBox 将消息记录到屏幕的应用程序.更新函数使用一些 Win32 函数来确保框自动滚动到末尾,除非用户正在查看另一行.这里是更新函数:
I have an application that logs messages to the screen using a TextBox. The update function uses some Win32 functions to ensure that the box automatically scrolls to the end unless the user is viewing another line. Here is the update function:
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 时它使用的内存).增长总是至少是我预期的两倍,并且(如我的示例中)有时更多.
Now the strange thing is that the text box consumes at least double the memory that I would expect it to. For example, when there are ~1MB of messages in the TextBox, the application can consume up to 6MB of memory (in addition to what it uses when the logToScreen is set to false). The increase is always at least double what I expect, and (as in my example) sometimes more.
更奇怪的是使用:
this.txtBoxLogging.Clear();
for (int i = 0; i < 3; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
不释放内存(事实上,它略有增加).
Does not free the memory (in fact, it increases slightly).
知道我记录这些消息时内存的去向吗?我不相信它与 Win32 调用有任何关系,但我已经将它包括在内以彻底.
Any idea where the memory is going as I'm logging these messages? I don't believe it has anything to do with the Win32 calls, but I've included it to be thorough.
我收到的前几个回复与如何跟踪内存泄漏有关,所以我想我应该分享我的方法.我结合使用 WinDbg 和 perfmon 来跟踪一段时间内的内存使用情况(从几个小时到几天).所有 CLR 堆上的总字节数没有增加超过我的预期,但是随着更多消息的记录,私有字节的总数稳步增加.这降低了 WinDbg 的用处,因为它的工具 (sos) 和命令(dumpheap、gcroot 等)基于 .NET 的托管内存.
The first couple of responses I got were related to how to track a memory leak, so I thought I should share my methodology. I used a combination of WinDbg and perfmon to track the memory use over time (from a couple hours to days). The total number of bytes on all CLR heaps does not increase by more than I expect, but the total number of private bytes steadily increases as more messages are logged. This makes WinDbg less useful, as it's tools (sos) and commands (dumpheap, gcroot, etc.) are based on .NET's managed memory.
这可能是 GC.Collect() 无法帮助我的原因,因为它只是在 CLR 堆上寻找空闲内存.我的泄漏似乎在非托管内存中.
This is likely why GC.Collect() can not help me, as it is only looking for free memory on the CLR heap. My leak appears to be in un-managed memory.
推荐答案
你是如何确定内存使用的?您必须查看应用程序的 CLR 内存使用情况,而不是系统为整个应用程序使用的内存(您可以使用 Perfmon).也许您已经在使用正确的监控方法了.
How are you determining the memory usage? You'd have to watch the CLR memory usage for your application, not the memory used by the system for the whole application (you can use Perfmon for that). Perhaps you are already using the correct method of monitoring.
在我看来,您在内部使用 StringBuilder
.如果是这样,那就可以解释内存加倍的原因,因为这就是 StringBuilder 内部的工作方式.
It seems to me that you are using StringBuilder
internally. If so, that would explain the doubling of the memory, because that's the way StringBuilder works internally.
如果对您的对象的引用仍在范围内,或者您的任何代码使用静态变量,则 GC.Collect()
可能不会执行任何操作.
The GC.Collect()
may not do anything if the references to your objects are still in scope, or if any of your code uses static variables.
我将保留上面的内容,因为它可能仍然是正确的,但我查看了 AppendText
的内部结构.它不附加(即附加到 StringBuilder),而是设置 SelectedText
属性,该属性不设置字符串而是发送 Win32 消息(字符串在检索时被缓存).
I'll leave the above, because it may still be true, but I looked up AppendText
's internals. It does not append (i.e., to a StringBuilder), instead, it sets the SelectedText
property, which does not set a string but sends a Win32 message (string is cached on retrieval).
因为字符串是不可变的,这意味着对于每个字符串,将有三个副本:一个在调用应用程序中,一个在基本 Control
的缓存"中,另一个在实际的 Win32文本框控件.每个字符有两个字节宽.这意味着任何 1MB 的文本都会消耗 6MB 的内存(我知道,这有点简单,但基本上看起来就是这样).
Because strings are immutable, this means, for every string, there will be three copies: one in the calling application, one in the "cache" of the base Control
and one in the actual Win32 textbox control. Each character is two bytes wide. This means that any 1MB of text, will consume 6MB of memory (I know, this is a bit simplistic, but that's basically what seems happening).
编辑 2: 不确定它是否会做出任何改变,但您可以考虑自己调用 SendMessage
.但它确实开始看起来你需要你自己的滚动算法和你自己的所有者绘制的文本框来减少内存.
EDIT 2: not sure if it'll make any change, but you can consider calling SendMessage
yourself. But it sure starts to look like you'll need your own scrolling algorithm and your own owner-drawn textbox to get the memory down.
这篇关于自动滚动文本框使用的内存超出预期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!