簡體   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