繁体   English   中英

事件如何导致C#中的内存泄漏?弱引用如何帮助缓解这种情况?

[英]How do events cause memory leaks in C# and how do Weak References help mitigate that?

有两种方法(我知道)在C#中导致无意的内存泄漏:

  1. 不处理实现IDisposable的资源
  2. 不正确地引用和取消引用事件。

我真的不明白第二点。 如果源对象的生命周期比侦听器长,并且当没有其他引用时,侦听器不再需要事件,则使用普通的.NET事件会导致内存泄漏:源对象将侦听器对象保存在内存中应该是垃圾收集。

你能用C#中的代码解释事件如何导致内存泄漏,以及如何使用弱引用和没有弱引用来编写代码来解决它?

当侦听器将事件侦听器附加到事件时,源对象将获得对侦听器对象的引用。 这意味着在分离事件处理程序或收集源对象之前,垃圾收集器无法收集侦听器。

考虑以下类:

class Source
{
    public event EventHandler SomeEvent;
}

class Listener
{
    public Listener(Source source)
    {
        // attach an event listner; this adds a reference to the
        // source_SomeEvent method in this instance to the invocation list
        // of SomeEvent in source
        source.SomeEvent += new EventHandler(source_SomeEvent);
    }

    void source_SomeEvent(object sender, EventArgs e)
    {
        // whatever
    }
}

...然后是以下代码:

Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;

即使我们为listener分配null ,它也不符合垃圾收集的条件,因为newSource仍然保持对事件处理程序的引用( Listener.source_SomeEvent )。 要解决此类泄漏,在不再需要事件侦听器时始终分离事件侦听器非常重要。

编写上述示例以关注泄漏问题。 为了修复该代码,最简单的方法可能是让Listener保持对Source的引用,以便以后可以分离事件监听器:

class Listener
{
    private Source _source;
    public Listener(Source source)
    {
        _source = source;
        // attach an event listner; this adds a reference to the
        // source_SomeEvent method in this instance to the invocation list
        // of SomeEvent in source
        _source.SomeEvent += source_SomeEvent;
    }

    void source_SomeEvent(object sender, EventArgs e)
    {
        // whatever
    }

    public void Close()
    {
        if (_source != null)
        {
            // detach event handler
            _source.SomeEvent -= source_SomeEvent;
            _source = null;
        }
    }
}

然后调用代码可以发信号通知它是使用对象完成的,这将删除Source对'Listener`的引用;

Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;

阅读Jon Skeet关于事件的精彩文章 这不是传统意义上的真正的“内存泄漏”,而是更多的未被断开的保持引用。 所以要记住-=一个事件处理程序,你在前一点+=并且你应该是金色的。

严格来说,在托管的.NET项目的“沙箱”中没有“内存泄漏”; 只有开发人员认为必要的参考时间才会更长。 弗雷德里克有权利; 当您将处理程序附加到事件时,因为处理程序通常是实例方法(需要实例),所以只要维护此引用,包含侦听器的类的实例就会保留在内存中。 如果侦听器实例依次包含对其他类的引用(例如,对包含对象的反向引用),则在侦听器退出所有其他范围之后,堆可以保持很长时间。

也许对Delegate和MulticastDelegate有更多深奥知识的人可以对此有所了解。 我看到它的方式,如果满足以下所有条件,则可能出现真正的泄漏:

  • 事件侦听器需要通过实现IDisposable来释放外部/非托管资源,但它要么没有,要么
  • 事件多播委托不会从其重写的Finalize()方法调用Dispose()方法,并且
  • 包含事件的类不会通过自己的IDisposable实现或Finalize()在委托的每个Target上调用Dispose()。

我从来没有听说过任何涉及在委托目标上调用Dispose()的最佳实践,更不用说事件监听器了,所以我只能假设.NET开发人员知道他们在这种情况下做了什么。 如果这是真的,并且事件后面的MulticastDelegate尝试正确处理侦听器,那么所有必要的是在需要处理的侦听类上正确实现IDisposable。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM