Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
事件如何导致C#中的内存泄漏?弱引用如何帮助缓解这一问题?_C#_.net_Events_Weak References - Fatal编程技术网

事件如何导致C#中的内存泄漏?弱引用如何帮助缓解这一问题?

事件如何导致C#中的内存泄漏?弱引用如何帮助缓解这一问题?,c#,.net,events,weak-references,C#,.net,Events,Weak References,有两种方法(据我所知)会导致C#中的无意内存泄漏: 不处理实现IDisposable的资源 错误地引用和取消引用事件 我不太明白第二点。如果源对象的生存期比侦听器长,并且在没有其他引用的情况下侦听器不再需要这些事件,则使用正常的.NET事件会导致内存泄漏:源对象在内存中保存应进行垃圾收集的侦听器对象 你能解释一下事件如何通过C#中的代码导致内存泄漏,以及我如何通过代码使用弱引用和不使用弱引用来绕过它吗?当侦听器将事件侦听器附加到事件时,源对象将获得对侦听器对象的引用。这意味着在分离事件处理程序或

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

  • 不处理实现IDisposable的资源
  • 错误地引用和取消引用事件
  • 我不太明白第二点。如果源对象的生存期比侦听器长,并且在没有其他引用的情况下侦听器不再需要这些事件,则使用正常的.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;
    
    即使我们将
    null
    分配给
    listener
    ,它也不符合垃圾收集的条件,因为
    newSource
    仍保留对事件处理程序的引用(
    listener.source\u 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;
    

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

    也许有人对委托和多播委托有一些更深奥的知识,可以对此有所帮助。在我看来,如果以下所有情况都是真实的,那么真正的泄漏是可能的:

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

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

    阅读Jon Skeet的优秀事件。它不是经典意义上的真正的“内存泄漏”,而是一个尚未断开连接的保留引用。因此,请始终记住
    -=
    一个事件处理程序,在上一点上你应该是金色的。

    我不认为这是完全正确的。CLR本身内的循环引用应自行处理(可以检测到)。真正的问题是在跨运行时处理时——COM互操作是一个很好的例子。这是因为CLR无法充分“查看”COM(非CLR)运行时/主机,因此无法检测和清理循环引用。我不确定这会对一些标准的.NET组件造成什么影响,例如WinForms(运行到本机上)。@pst:没有循环引用;请注意,
    Listener
    不包含对
    源代码的引用<代码>侦听器
    保持活动状态,因为
    包含对
    侦听器
    的引用。只要某个内容引用了
    源代码
    监听器
    就无法收集,但是
    监听器
    并没有阻止
    源代码
    被收集。您应该编辑您的帖子,告诉人们如何取消引用事件处理程序以及在何处执行。@pst,这不是循环引用的问题,而是对象生命周期的问题。这个例子不太好,但它似乎试图表明事件源超出了侦听器,在这种情况下,在事件源不再具有引用或侦听器从事件源显式删除其事件处理程序之前,将不会收集侦听器。一个典型的Windows窗体将侦听其子控件的事件,在这种情况下,事件源的生存期较短,因此窗体在这个典型的WinForms场景中没有内存泄漏,其他场景也存在,并且经常会发生。@jnvjgt@Avatar否,它不会。将
    null
    分配给侦听器在这里实际上没有任何作用,只是告诉垃圾收集器该项已准备好收集;不管怎样,一旦离开街区它就会这么做。很少有必要将对象设置为null,以便在它离开块后将其收集,但一些程序员将其放置在那里,以明确表示他们
    null
    “已释放该对象。您也可以将System.Runtime.InteropServices.Marshal.AllocHGlobal释放它,但无法释放它。。。旧式的泄密更难,但不容易