我有一个C#Winforms应用程序,在其中按一个按钮实例化对象,订阅其事件,然后根据该对象的方法启动线程。该对象的方法使用大量内存,但是一旦线程完成,我认为在调用GC.Collect()
时应该将其发布。但是,情况似乎并非如此。该程序可以使用千兆字节的内存,因此这不是一个小问题,并且似乎只在关闭程序或再次按下按钮时才会发布。
这是一个示例应用程序,可证明问题:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Worker worker;
private void button1_Click(object sender, EventArgs e)
{
worker = new Worker();
worker.Finished += worker_Finished;
Thread thread = new Thread(new ThreadStart(worker.DoWork));
thread.IsBackground = true;
thread.Start();
}
void worker_Finished(object sender, EventArgs e)
{
worker.Finished -= worker_Finished;
GC.Collect();
MessageBox.Show((Environment.WorkingSet / 1024 / 1024).ToString() + " MB in use");
}
}
public class Worker
{
public event EventHandler Finished;
protected virtual void OnFinished(EventArgs e)
{
EventHandler handler = Finished;
if(handler != null)
{
handler(this, e);
}
}
public void DoWork()
{
Random random = new Random();
string[] list = new string[10000000];
for(int i = 0; i < list.Length; i++)
{
list[i] = random.NextDouble().ToString();
}
//list = null;
OnFinished(new EventArgs());
}
}
}
请注意,在这种情况下,不确定list = null;
行似乎可以解决问题,尽管我不确定为什么。在我的实际应用程序中,我尝试在功能结束之前将所有大对象设置为null,但似乎无济于事。因此,这不是对问题的完美娱乐,但希望有人可以解释这里发生的事情,这可以帮助我解决实际问题。
另外,我知道这个问题非常相似,但是就我而言,我明确强迫垃圾收集。
垃圾收集是一个复杂的事件。这不仅是收回所有使用的内存的情况。对象之间存在复杂的关系,因此GC必须确保它不会清理仍然可以访问某个地方的对象。
因为您正在陶式方法完成之前提高事件,因此"列表"变量仍在范围内,因此在呼叫收集时无法清理其内容。通过将变量设置为null,您可以删除对数组的引用,因此可以删除其所指的所有字符串对象,因此可以通过呼叫收集的呼叫来回收它们的所有内存。
有问题的内存是正在发布,只是在任务管理器或Environment.WorkingSet
Working Set
和Private Bytes
内存之间存在差异(此处更多)。Private Bytes
是您的过程实际使用的,Working Set
还包含您过程中"坐落"但可以被其他人使用的内存。
仅查看Private Bytes
在Task Manager
中添加特定列或更高,请使用Performance Monitor
。
确实,如果没有//list = null;
,list
在检查Environment.WorkingSet
时仍然具有对数组的引用,但这与伪代码有关,而不是问题本身。
尝试此方案。将实际的工作放入私有方法,并让公共方法称其为称,当返回返回时(请参阅注释)时,这避免了必须将变量隐式设置为null
,因为它们会在DoActualWork()
public class Worker
{
public event EventHandler Finished;
protected virtual void OnFinished(EventArgs e)
{
EventHandler handler = Finished;
if(handler != null)
{
handler(this, e);
}
}
private void DoActualWork() {
Random random = new Random();
string[] list = new string[10000000];
for(int i = 0; i < list.Length; i++)
{
list[i] = random.NextDouble().ToString();
}
}
public void DoWork()
{
DoActualWork();
OnFinished(EventArgs.Empty); // This is preferred.
}
}
就像jmcilhinney所说的那样,gc.collect()在列表仍处于范围中,因此从未收集。通过将完成事件的调用与实际工作方法分开,您可以完全避免。