程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 未釋放事件Handler可能導致內存洩漏

未釋放事件Handler可能導致內存洩漏

編輯:關於.NET

以前曾看見過這樣一個問題:托管代碼會不會導致內存洩漏。自己對GC的了解也不是很深,但還是比較贊成這樣的觀點:托管代碼不會產生內存洩漏,除非你沒有正確釋放非托管資源。

今天看到一個非常有趣的例子,關於沒有釋放事件的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字節,哪位高手賜教一下啊:)

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved