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

发布于 2024-09-18 07:39:03 字数 257 浏览 5 评论 0原文

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

  1. 不处置实现 IDisposable 的资源
  2. 错误地引用和取消引用事件。

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

您能否解释一下事件如何导致 C# 代码中的内存泄漏,以及如何使用弱引用和不使用弱引用进行编码来绕过它?

There are two ways (that I know of) to cause an unintentional memory leak in C#:

  1. Not disposing of resources that implement IDisposable
  2. Referencing and de-referencing events incorrectly.

I don't really understand the second point. If the source object has a longer lifetime than the listener, and the listener doesn't need the events anymore when there are no other references to it, using normal .NET events causes a memory leak: the source object holds listener objects in memory that should be garbage collected.

Can you explain how events can cause memory leaks with code in C#, and how I can code to get around it using Weak References and without Weak References?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

谁对谁错谁最难过 2024-09-25 07:39:03

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

考虑以下类:

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_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;

When a listener attaches an event listener to an event, the source object will get a reference to the listener object. This means that the listener cannot be collected by the garbage collector until either the event handler is detached, or the source object is collected.

Consider the following classes:

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
    }
}

...and then the following code:

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

Even though we assign null to listener, it will not be eligible for garbage collection, since newSource is still holding a reference to the event handler (Listener.source_SomeEvent). To fix this kind of leak, it is important to always detach event listeners when they are no longer needed.

The above sample is written to focus on the problem with the leak. In order to fix that code, the easiest will perhaps be to let Listener hold on to a reference to Source, so that it can later detach the event listener:

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;
        }
    }
}

Then the calling code can signal that it is done using the object, which will remove the reference that Source has to ´Listener`;

Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;
说不完的你爱 2024-09-25 07:39:03

阅读 Jon Skeet 关于事件的精彩文章。这不是经典意义上的真正的“内存泄漏”,而更多的是尚未断开连接的保留引用。因此,请始终记住 -= 一个您在之前的点 += 的事件处理程序,您应该是黄金。

Read Jon Skeet's excellent article on events. It's not a true "memory leak" in the classic sense, but more of a held reference that hasn't been disconnected. So always remember to -= an event handler that you += at a previous point and you should be golden.

被翻牌 2024-09-25 07:39:03

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

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

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

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

There are, strictly speaking, no "memory leaks" within the "sandbox" of a managed .NET project; there are only references held longer than the developer would think necessary. Fredrik has the right of it; when you attach a handler to an event, because the handler is usually an instance method (requiring the instance), the instance of the class containing the listener stays in memory as long as this reference is maintained. If the listener instance contains references to other classes in turn (e.g. backreferences to containing objects), the heap can stay quite large long after the listener has gone out of all other scopes.

Maybe someone with a bit more esoteric knowledge of Delegate and MulticastDelegate can shed some light on this. The way I see it, a true leak COULD be possible if all of the following were true:

  • The event listener requires external/unmanaged resources to be released by implementing IDisposable, but it either does not, or
  • The event multicast delegate does NOT call the Dispose() methods from its overridden Finalize() method, and
  • The class containing the event does not call Dispose() on each Target of the delegate through its own IDisposable implementation, or in Finalize().

I've never heard of any best practice involving calling Dispose() on delegate Targets, much less event listeners, so I can only assume the .NET developers knew what they were doing in this case. If this is true, and the MulticastDelegate behind an event tries to properly dispose of listeners, then all that is necessary is proper implementation of IDisposable on a listening class that requires disposal.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文