我们经常会写EventHandler += AFunction; 如果没有手动注销这个Event handler类似:EventHandler –= AFunction 会内存泄露吗?会! 这个Event handler的问题是内存泄露的几大元凶之一。通常观察者模式就是用EventHandler来实现,也容易内存泄露。话虽如此,但要分清楚几个情况,不要谈虎色变。其实大多数有时候我们没必要手动注销EventHandler,也没有内存泄露。看下面的例子:

 

没有手动注销Event handler也没有内存泄露

class TestClassHasEvent
   2: {
object sender, EventArgs e);
event TestEventHandler YourEvent;
void OnYourEvent(EventArgs e)
   6:     {
this, e);
   8:     }
   9: }
  10:  
class TestListener 
  12: {
byte[1000000];
  14:  
private TestClassHasEvent _inject;
  16:  
public TestListener(TestClassHasEvent inject)
  18:     {
  19:         _inject = inject;
new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
  21:     }
  22:     
object sender, EventArgs e)
  24:     {
  25:         
  26:     }
  27: }
  28:  
class Program
  30: {
void DisplayMemory()
  32:     {
true));
  34:     }
  35:  
void Main()
  37:     {
  38:         DisplayMemory();
  39:         Console.WriteLine();
int i = 0; i < 5; i++)
  41:         {
, i + 1);
  43:  
new TestClassHasEvent());
////listener = null; //可有可无
  46:             
  47:             GC.Collect();
  48:             GC.WaitForPendingFinalizers();
  49:             GC.Collect();
  50:             DisplayMemory();
  51:             
  52:         }
  53:         Console.Read();
  54:     }
  55: }    

运行结果:

[.NET]Event handler没有注销就会内存泄露吗?

 

没有手动注销Event handler内存泄露

上面的例子没有注销事件,也没有内存泄露。我们来改一行代码,就内存泄露了:

把下面这段:

public TestListener(TestClassHasEvent inject)
   2: {
   3:     _inject = inject;
new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
   5: }

改成:

public TestListener(TestClassHasEvent inject)
   2: {
new EventHandler(SystemEvents_DisplaySettingsChanged);
   4: }
   5:  
object sender, EventArgs e)
   7: {
   8:   
   9: }

这样就内存泄露了,看看运行结果:

[.NET]Event handler没有注销就会内存泄露吗?

加个Dispose手动注销事件,然后使用Using关键字,就没有问题了,不会内存泄露了:

   1:  
   2:  
class TestListener : IDisposable
   4: {
byte[1000000];
   6:  
private TestClassHasEvent _inject;
   8:  
public TestListener(TestClassHasEvent inject)
  10:     {
new EventHandler(SystemEvents_DisplaySettingsChanged);
  12:     }
  13:  
object sender, EventArgs e)
  15:     {
  16:       
  17:     }
  18:     
#region IDisposable Members
  20:  
void Dispose()
  22:     {
new EventHandler(SystemEvents_DisplaySettingsChanged);
  24:     }
  25:  
#endregion
  27: }
  28:  
class Program
  30: {
void DisplayMemory()
  32:     {
true));
  34:     }
  35:  
void Main()
  37:     {
  38:         DisplayMemory();
  39:         Console.WriteLine();
int i = 0; i < 5; i++)
  41:         {
, i + 1);
  43:             
new TestClassHasEvent()))
  45:             {
//do something
  47:             }
  48:             GC.Collect();
  49:             GC.WaitForPendingFinalizers();
  50:             GC.Collect();
  51:             DisplayMemory();
  52:             
  53:         }
  54:         Console.Read();
  55:     }
  56: }

 

上面两个例子一个内存泄露,一个没有内存泄露,我想你应该知道原因了,根本区别在于后者有个SystemEvents.DisplaySettingsChanged事件,来看看这个静态Static事件的定义:

// Type: Microsoft.Win32.SystemEvents
// Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll
   4:  
using System;
using System.ComponentModel;
   7:  
namespace Microsoft.Win32
   9: {
class SystemEvents
  11:     {
int interval);
void InvokeOnEventsThread(Delegate method);
void KillTimer(IntPtr timerId);
event EventHandler DisplaySettingsChanging;
event EventHandler DisplaySettingsChanged;
event EventHandler EventsThreadShutdown;
event EventHandler InstalledFontsChanged;
  19:  
  20:         [EditorBrowsable(EditorBrowsableState.Never)]
)]
false)]
event EventHandler LowMemory;
  24:  
event EventHandler PaletteChanged;
event PowerModeChangedEventHandler PowerModeChanged;
event SessionEndedEventHandler SessionEnded;
event SessionEndingEventHandler SessionEnding;
event SessionSwitchEventHandler SessionSwitch;
event EventHandler TimeChanged;
event TimerElapsedEventHandler TimerElapsed;
event UserPreferenceChangedEventHandler UserPreferenceChanged;
event UserPreferenceChangingEventHandler UserPreferenceChanging;
  34:     }
  35: }

 

注意Static,注意Singleton

这种static的东西生命周期很长,永远不会被GC回收,一旦被他给引用上了,那就不可能释放了。上面的例子就是SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);那就意味着这个类被SystemEvents.DisplaySettingsChanged 引用了,通过它的函数。另外一个要注意的是Singleton单例模式实现的类,他们也是static的生命周期很长,要注意引用链,你的类是否被它引用上,如果在它的引用链上,就内存泄露了。

 

注意永远不释放的东西

还有一种情况,既不是你的对象被static对象而不能释放,也不是Singleton,而是你的对象被一个永远不释放的对象引用着,这个对象或许不是static的。这种类型很多,比如你的界面有个MainForm,嘿嘿,这个MainForm永远不会关闭和释放的,被它引用了那就不会释放了。看个例子:

MainForm里面有个public event,MainForm里面打开Form2,然后关闭,看看Form2能不能释放:

class MainForm : Form
   2: {
event PropertyChangedEventHandler PropertyChanged;
   4:  
string propertyName)
   6:     {
   7:         PropertyChangedEventHandler handler = PropertyChanged;
   8:  
null)
new PropertyChangedEventArgs(propertyName));
  11:     }
  12:  
public MainForm()
  14:     {
  15:         InitializeComponent();
  16:     }
  17:  
object sender, EventArgs e)
  19:     {
new Form2();
  21:  
this.PropertyChanged += frm.frm_PropertyChanged; 
//MainForm referenced form2, because main form is not released, therefore form2 will not released.
  24:  
  25:         DialogResult d = frm.ShowDialog();
  26:         
  27:         GC.Collect();
  28:         ShowTotalMemory();
  29:  
  30:     }
  31:  
  32:     
  33:  
void ShowTotalMemory()
  35:     {
true)));
  37:     }
  38: }

Form2里面有个函数:

class Form2 : Form
   2: {
public Form2()
   4:     {
   5:         InitializeComponent();
   6:     }
object sender, PropertyChangedEventArgs e)
   8:     {
   9:  
  10:     }
  11: }

所以这种情况下,你的Event handler没有手动注销,那就肯定内存泄露了。

 

WeakReference就能解决问题吗?

和强引用对应的,有个弱引用(WeakReference),和Event handler对应的还有WeakEventHdnler。甚至还有WeakPropertyChangedListener。都是号称可以一劳永逸的东东,也就是说用了这些东西,内存肯定不会泄露,即使你没有手动去注销Event handler。看起来似乎问题解决了。几个问题:

  • WeakReference和强引用在创建和调用的时候有几十倍的性能差(40-50倍),而且WeakRefence有专门的对象管理器来贮存这些弱引用对象,扫描它们是否还用得着,维护这个列表也需要开销。
  • 引用链上的情况很复杂:假设A弱引用b,而从b –> c –> d这条引用链条上有内存泄露,还是一样,内存泄露。也就是说:b不会释放,尽管它是弱引用的。

 

深入思考和继续阅读

通常.NET程序的内存泄露原因:

  • Static references
  • Event with missing unsubscription
  • Static event with missing unsubscription
  • Dispose method not invoked
  • Incomplete Dispose method

有关如何避免.NET程序的内存泄露,请仔细阅读MSDN这两篇文章,详细讲述了<如何检测.NET程序内存泄露>以及<如何写高性能的托管程序>

有关.NET的自动内存管理机制、GC机制,垃圾回收原理等深层次内容,请仔细阅读下面的内容:

参考:园子里Artech关于Event handler问题的文章:

相关文章:

  • 2021-07-13
  • 2021-10-14
  • 2022-12-23
  • 2022-12-23
  • 2021-11-13
  • 2022-12-23
  • 2022-01-02
猜你喜欢
  • 2022-12-23
  • 2021-12-07
  • 2022-12-23
  • 2021-07-04
  • 2021-09-13
  • 2021-12-03
  • 2022-01-05
相关资源
相似解决方案