您知道嗎:未釋放事件Handler可能導致記憶體流失

來源:互聯網
上載者:User

以前曾看見過這樣一個問題:Managed 程式碼會不會導致記憶體流失。自己對GC的瞭解也不是很深,但還是比較贊成這樣的觀點:Managed 程式碼不會產生記憶體流失,除非你沒有正確釋放非託管資源。
今天看到一個非常有趣的例子,關於沒有釋放事件的Handler導致的記憶體流失。
以前對於釋放Handler的觀念是一點也沒有,這主要因為沒此方面的意識,沒有養成好的習慣。只知道當關心這個事件的時候就註冊一下, 暫時不關心了就移除掉。卻從來沒有想到最終不移除不必要的Handler會導致此類無法被正常回收,導致不必要的記憶體浪費。

事情是這樣的,今天在看項目Source Code的時候發現一個有趣的字眼:"WeakEvent". 自己以前對WeakReference有點瞭解,所以就好奇地看看這是個啥玩意。
發現其是一種通過弱引用實現的Delegate。因為沒有太多的注釋,所有不知其為啥用此種方式來封裝事件。於是順手Google了一下,找到了一篇關於weak event的非常有意思的文章。
文章裡提出了一個問題,情境如下:

UnRelease Event Handler
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;

namespace ConsoleApplication16
{
    class DisplaySettingsListener
    {
        byte[] m_ExtraMemory = new byte[1000000];

        public DisplaySettingsListener()
        {
            SystemEvents.DisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged);
        }

        private void ehDisplaySettingsChanged(object sender, EventArgs e)
        {
        }
    }

    class Program
    {
        static void DisplayMemory()
        {
            Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        }

        static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                DisplaySettingsListener listener = new DisplaySettingsListener();
                listener = null;
                GC.Collect();

                DisplayMemory();
            }
            Console.Read();
        }

    }
}

啟動並執行結果如下:

 

雖然我們釋放了對listener的引用,並且強制GC進行回收,但我們可以看到其記憶體佔用量還是變大了,出乎了我的意料。
這就是該文作者指出的事件列表裡儲存的是一個強引用而非弱引用。雖然上面釋放了listener變數對Listener執行個體的引用,但因為仍然在DisplaySettingsChanged事件列表裡儲存了對Listener執行個體的引用,導致Listener執行個體並不能被記憶體回收(有人引用,自然不會回收)。
那麼接下來看看下面的代碼:

Release Event Hanlder
    class DisplaySettingsListener : IDisposable
    {
        byte[] m_ExtraMemory = new byte[1000000];

        public DisplaySettingsListener()
        {
            SystemEvents.DisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged);
        }

        private void ehDisplaySettingsChanged(object sender, EventArgs e)
        {
        }

        IDisposable Members#region IDisposable Members

        public void Dispose()
        {
            SystemEvents.DisplaySettingsChanged -= new EventHandler(ehDisplaySettingsChanged);
        }

        #endregion
    }

    class Program
    {
        static void DisplayMemory()
        {
            Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        }

        static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                DisplaySettingsListener listener = new DisplaySettingsListener();
                listener.Dispose();
                listener = null;
                GC.Collect();

                DisplayMemory();
            }
            Console.Read();
        }

    }

運行結果如下:

 
結果是不是正如您猜測的呢:)。已經成功地回收了listener執行個體。 不知為何從432944位元組變到446980位元組,哪位高手賜教一下啊:)
詳情可以看原文    The Problem With Delegates
在後續的文章中作者類似文章開頭提到的Weak Event來解決這個問題: Solving the Problem with Events: Weak Event Handlers

也許您覺得寫這樣的一個Weak Event沒有必要或者顯得麻煩,但您一定要記得及時地在必要的地方調用 -= 取消不再關心的事件。本文的目的也只是在此方面提個善意的提醒。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.