【问题标题】:Object doesn't get garbage collected对象没有被垃圾收集
【发布时间】:2011-10-12 16:25:08
【问题描述】:

我认为这是一个 C# 初学者问题,但我似乎找不到正确的解决方案。

我有一个 ClassOne 对象,它定义了一个事件。 我创建了一个 ClassTwo 对象,它被认为是一个黑盒子,这意味着我不知道它是否会注册到任何事件。 ClassTwo 构造函数注册到 ClassOne 的事件。 当 ClassTwo 对象超出范围时,问题就来了。垃圾收集器从不删除此对象,因为它从未取消注册该事件。

所以我有两个问题:

  1. 有没有办法让 ClassTwo 对象知道何时超出范围?对于老 C++ 程序员来说,这将在析构函数中,但对于 C#,这不起作用。

  2. 是否有调试工具可以帮助我找到此类对象?

这里是重现问题的示例代码:

    public partial class MainWindow : Window
{
    static public ClassOne classOne = new ClassOne();
    public MainWindow()
    {
        InitializeComponent();
        ClassTwo classtwo = new ClassTwo();
    }

    private void buttonTest_Click(object sender, RoutedEventArgs e)
    {
        GC.Collect();
    }
}
public class ClassOne
{
    public ClassOne()
    {
        Trace.WriteLine(this + " constructor");
    }

    ~ClassOne()
    {
        Trace.WriteLine(this + " destructor");
    }

    public delegate void UpdateFunc(object sender, EventArgs args);
    public event UpdateFunc OnUpdate;

}
public class ClassTwo
{
    public ClassTwo()
    {
        Trace.WriteLine(this + " constructor");
        MainWindow.classOne.OnUpdate += new ClassOne.UpdateFunc(classOne_OnUpdate);
    }

    void classOne_OnUpdate(object sender, EventArgs args)
    {
        throw new NotImplementedException();
    }

    ~ClassTwo()
    {
        Trace.WriteLine(this + " destructor");
    }
}

【问题讨论】:

  • 您是否希望实现IDisposable
  • +1 优秀问题!事件比乍一看要复杂得多。
  • 附带说明,如果您将终结器称为析构函数,您可能应该阅读Everybody thinks about garbage collection the wrong way
  • @delnan:我认为 OP 已经理解了这个概念,这只是一个轻微的命名问题(即习惯)。这并不意味着 OP 没有正确理解它。
  • FWIW,该语言第一个版本的正式名称是析构函数。由于它造成的混乱,它被改变了。

标签: c# events garbage-collection destructor


【解决方案1】:

我会在这样的对象上实现IDisposable,并在Dispose 方法中从事件中注销。 你会像这样使用你的对象:

using(var two = new ClassTwo(classOne))
{
    // Do something with two
}
// object can now be garbage collected.

如果调用者未能调用Dispose,那么你就不走运了。

【讨论】:

  • 我想我理解 IDisposable 的概念,但这依赖于调用 Dispose 的对象的用户。通常一个对象应该自己清理东西,而不是依赖调用者。你是说没有办法做到这一点?我的第二个问题呢?调试器能帮我找到这些永远不会被删除的对象吗?作为一个初学者,我现在有一个相当大的程序,偶然发现我的一些对象没有被删除。
  • @MTR:您可以使用内存分析器来检测这样的对象。使用IDisposable 是要走的路——你的对象不知道什么时候不再需要它。只有调用者才能知道。
  • 你能推荐一个好的免费内存分析器吗?您如何看待这个解决方案:blogs.msdn.com/b/greg_schechter/archive/2004/05/27/143605.aspx
  • 我建议您只使用其中一种商业内存配置文件。他们都提供试用版,例如JetBrains dotTrace。博文中讨论的解决方案在大多数情况下都是多余的,因为大多数情况下,实现IDisposable 就足够了。如果由于某种原因这对您不起作用,请使用博文中的解决方案。
【解决方案2】:
  1. 除非它实现了IDisposable 并且调用者通过正确调用Dispose来配合。 (当然,调用者为什么不配合?)

  2. 我没有。 :( 我认为你最好的办法是实现IDisposable 并在Dispose 上注销。

【讨论】:

    【解决方案3】:

    正如其他人所提到的,Dispose 模式是解决此问题的方法。如果 ClassTwo 对象存在的时间很短,您可以使用 C# using 语句确保在您使用完对象后调用 Dispose:

    using (var foo = new ClassTwo())
    {
        foo.Bar();
    }
    

    为了找到此类问题的根本原因,您需要使用内存分析器。 dotTrace 已经提到过,另一个不错的是SciTech Memory Profiler。您需要找到您认为应该被垃圾收集但不是的对象的根路径。根路径是未收集对象的原因 - 因为通过传递引用,保证活着的对象(GC 根)正在引用您想死的对象。

    这些内存分析器非常有助于确定哪些 GC 根给您带来了麻烦,以及从根到您的对象的引用路径是什么。沿着该根路径的某处将是一个不合适的引用,并且是问题的原因。

    在您的代码中,未收集 ClassTwo 对象的原因可能是 MainWindow 具有对 ClassOne 对象的静态引用,或者从 ClassOne 到 ClassTwo 的事件处理程序尚未取消挂钩。静态引用是 GC Roots 的一个例子 - 所以所有 classOne 引用都将是活动的,直到 MainWindow 中的静态引用被更改。

    问题是静态还是事件处理程序取决于您的应用程序的场景 - ClassOne 对象也应该被收集 - 对它进行静态引用是错误的吗?或者是静态引用所需的行为 - classOne 是一个长期存在的对象,而 classTwo 是一个短暂存在的对象,在这种情况下,classTwo 应该在其生命周期结束时被 Disposed,并且 Dispose 应该解除事件处理程序的挂钩。

    这是一篇了解 .Net GC 的好文章,由 Jeffrey Richter 撰写:http://msdn.microsoft.com/en-us/magazine/bb985010.aspx。它现在有点老了,近年来 GC 有了新的添加,但它是一个很好的起点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      相关资源
      最近更新 更多