为什么即使在强制垃圾收集时也不会释放的线程使用内存



我有一个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 SetPrivate Bytes内存之间存在差异(此处更多)。Private Bytes是您的过程实际使用的,Working Set还包含您过程中"坐落"但可以被其他人使用的内存。

仅查看Private BytesTask 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()在列表仍处于范围中,因此从未收集。通过将完成事件的调用与实际工作方法分开,您可以完全避免。

最新更新