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

jnv*_*jgt 27 .net c# events weak-references

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

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

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

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

Fre*_*örk 35

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

考虑以下类:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

...然后是以下代码:

Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;
Run Code Online (Sandbox Code Playgroud)

尽管我们分配nulllistener,它不会被垃圾收集,因为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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;
Run Code Online (Sandbox Code Playgroud)

  • @pst:没有循环引用; 注意`Listener`没有引用`Source`.`Listener`被保持活着,因为`Source`持有对`Listener`的引用.只要某些东西持有对"Source"的引用,就无法收集"listener",但是"Listener"并没有阻止收集"Source". (6认同)
  • 您应该编辑帖子,告诉人们如何取消引用事件处理程序和*where*来执行此操作. (2认同)
  • @pst,这不是循环引用的问题,而是对象生存期的问题.示例并不是很好,但似乎确实尝试显示事件源是监听器,在这种情况下,在事件源不再具有引用或侦听器从事件中显式删除事件处理程序之前,不会收集侦听器资源.一个典型的Windows窗体会监听它的子控件的事件,在这种情况下,事件源的生命周期较短,而Form在这个典型的WinForms场景中没有内存泄漏,其他场景存在并经常咬人. (2认同)
  • @jnvjgt @Avatar不,它不会.将"null"分配给监听器并不会在这里做任何事情,除了告诉垃圾收集器该项目已准备好被收集; 无论如何,它一旦离开街区就会做什么.很少有必要将一个对象设置为null以便在它离开块时将其收集,但是一些程序员将它放在那里以明确声明它们对该对象"null". (2认同)

Jes*_*cer 12

阅读Jon Skeet 关于事件的精彩文章.这不是传统意义上的真正的"内存泄漏",而是更多的未被断开的保持引用.因此,请始终记住-=+=之前的事件处理程序,您应该是金色的.