编辑 - 测量完成内存之前的空白线程
这是在Windows CE 5.0下运行的.NET Compact Framework 2.0。
我在开发我的应用程序时遇到了一些有趣的行为。每当我尝试创建一个表单并让它运行一个单独的线程时,它关闭时似乎泄漏392个字节,我不确定为什么。
到目前为止,我的方法是创建一个新线程并拥有它a)创建表单并不断调用Application.DoEvents直到它关闭或b)创建表单将其传递给Application.Run(Form)。
以下是说明问题的示例表单。
public partial class TestForm : Form
{
public TestForm()
{
InitializeComponent();
}
private void DoMemoryTest(bool useApplicationRun)
{
GC.WaitForPendingFinalizers();
GC.Collect();
long originalMem = GC.GetTotalMemory(true);
Thread t;
if (useApplicationRun)
t = new Thread(new ThreadStart(AppRunThread));
else
t = new Thread(new ThreadStart(DoEventThread));
t.Start();
Thread.Sleep(3000);//Dodgey hack
t.Join();
t = null;
GC.WaitForPendingFinalizers();
GC.Collect();
long terminatingMem = GC.GetTotalMemory(true);
MessageBox.Show(String.Format("An increase of {0} bytes was measured from {1} bytes",
terminatingMem - originalMem, originalMem));
}
private void button1_Click(object sender, EventArgs e)
{
DoMemoryTest(false);
}
private void button2_Click(object sender, EventArgs e)
{
DoMemoryTest(true);
}
private void AppRunThread()
{
Application.Run(new OpenCloseForm());
}
private void DoEventThread()
{
using (OpenCloseForm frm = new OpenCloseForm())
{
frm.Show();
do
{
Application.DoEvents();
} while (frm.Showing);
}
}
/// <summary>
/// Basic form that opens for a short period before shutting itself
/// </summary>
class OpenCloseForm : Form
{
public OpenCloseForm()
{
this.Text = "Closing Soon";
this.Size = new Size(100, 100);
this.TopMost = true;
}
public volatile bool Showing = false; //dodgy hack for DoEventThread
System.Threading.Timer timer;
protected override void OnLoad(EventArgs e)
{
Showing = true;
base.OnLoad(e);
timer = new System.Threading.Timer(new TimerCallback(TimerTick), null, 1000, 1000);
}
delegate void CloseDelegate();
private void TimerTick(object obj)
{
this.Invoke(new CloseDelegate(this.Close));
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
Showing = false;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
}
base.Dispose(disposing);
}
}
//Designer code to follow....
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.timer1 = new System.Windows.Forms.Timer();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(32, 47);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(116, 39);
this.button1.TabIndex = 1;
this.button1.Text = "DoEvents Loop";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(32, 115);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(116, 39);
this.button2.TabIndex = 2;
this.button2.Text = "Application.Run";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// TestForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(177, 180);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "TestForm";
this.Text = "TestForm";
this.TopMost = true;
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
我是否遗漏了有关处置控制权的问题?有没有人有任何关于从哪里去的想法?
提前致谢。
答案 0 :(得分:4)
好的,关于这段代码我要说的第一件事就是WTF?!您需要对Windows消息的工作方式进行一些研究。我会在这里介绍一下,但你真的需要先了解它,然后再尝试疯狂的东西,比如我在这里看到的东西。
所以你肯定会遇到一些问题。我不确定你想要实现的是什么的“大图”以及你如何注意到这个错误,但是这段代码告诉我你的架构中存在一些基本问题。
但是你认为你发现了这个泄漏怎么样?好吧,没有泄漏。您不了解CF(和托管)内存管理。同样,建议进行研究,但MSDN非常好webcast that covers it well。
这种情况的缺点是你在不同的线程上创建了一些对象。这些线程创建了一堆东西,其中一些是IDisposable,一些不是。当线断下时,那些不再有根的物品因此可以收集。当调用Collect时,GC会遍历所有根并记录每个具有根(标记)的对象。然后那些没有“被释放”(扫除)。它们所在的GC堆中的区域不再标记为正在使用中 - 有关详细信息,请参阅网络广播。如果该项目实现了IDisposabe,那么将获得一个新的根,因此终结器仍然可以存在并在下一个GC周期运行 。最后,终结器线程运行(非确定性)。
您的代码不考虑此行为。你没有运行两次收集。在Collect调用之后你还没有等待终结器(简单地调用WaitForPendingFinalizers可能还不够)。由于您的线程本身未被标记为后台线程,因此谁知道它们在生命周期中的位置以及GC使用状态可能是什么。
因此,当我们真正了解它时,问题是:你究竟想要解决什么?您正在托管内存环境中运行。除非你看到OOM,否则你几乎总是不应该担心内存水平 - 这就是首先拥有GC的重点。不要尝试建立一个复杂的,在哪里的Waldo学术练习,并让我们试图找到泄漏。
如果您确实遇到了问题,那么首先应确保您的应用程序设计与Windows应用程序的编写方式一致,然后使用tools like RPM来分析哪些根存储内存并修复您创建的泄漏(是的,泄漏仍然可以并且确实发生在托管代码中)。当然,您也可以在这里询问关于现实世界问题的合理问题。
修改强>
微软似乎已经清除了我上面提到的网络广播内容。希望他们能够找到并重新发布它,但同时(如果他们从未找到它)我至少拥有我用于MEDC和it's available on my blog的原始演讲的PowerPoint。
答案 1 :(得分:0)
我可能错了,因为我不是CF的专家,但除非系统处于内存压力之下,否则.net将不会释放它所采用的内存。
我的理解是,如果应用程序需要一次x字节的内存,那么在某些时候它可能至少需要x个字节。除非操作系统中的其他内容需要它,否则它不会释放该内存。
答案 2 :(得分:0)
首先,我会问为什么你要在其他线程上创建足够的表单,你关心的是392个字节,但这就是我想的那个。
我开始的第一个地方就是您在DoMemoryTest方法中没有删除的托管线程实例。在Thread.Sleep(3000)调用之后调用t.Join(),然后将其设置为null(因此即使在调试模式下也可以进行GC)。