首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

未解除事件訂閱導致記憶體洩漏:WeakEvent 模式與正確釋放實踐

     編輯:關於.NET

托管代碼也會洩漏記憶體——**事件發佈者持有訂閱者的強參考**,導致訂閱者無法被 GC 回收。本文以 SystemEvents.DisplaySettingsChanged 為例實測洩漏,並示範 IDisposable 手動解除與 WeakEvent 弱參考封裝兩種解法。

洩漏實測

class DisplaySettingsListener
{
    byte[] m_ExtraMemory = new byte[1_000_000]; // 1MB 壓力
    public DisplaySettingsListener()
    {
        SystemEvents.DisplaySettingsChanged += ehDisplaySettingsChanged;
    }
    void ehDisplaySettingsChanged(object s, EventArgs e) { }
}

for (int i=0; i<5; i++)
{
    var listener = new DisplaySettingsListener();
    listener = null;
    GC.Collect();
    Console.WriteLine(GC.GetTotalMemory(true)); // 記憶體持續增長
}

結果:每次迴圈記憶體增加 ~1MB,證實 listener 未被回收,因靜態事件仍持有委托參考。

解法 1:實作 IDisposable 手動解除

class DisplaySettingsListener : IDisposable
{
    byte[] m_ExtraMemory = new byte[1_000_000];
    public DisplaySettingsListener()
    {
        SystemEvents.DisplaySettingsChanged += ehDisplaySettingsChanged;
    }
    void ehDisplaySettingsChanged(object s, EventArgs e) { }
    public void Dispose()
    {
        SystemEvents.DisplaySettingsChanged -= ehDisplaySettingsChanged;
    }
}

// 使用
using (var listener = new DisplaySettingsListener()) { /* ... */ }
// 或
listener.Dispose(); listener = null; GC.Collect(); // 記憶體穩定

解法 2:WeakEvent 封裝弱參考委托

原理:事件存儲 WeakReference 指向目標物件,觸發時檢查目標是否存活,若已回收則自動移除。

public class WeakEvent
{
    private readonly List _handlers = new List();
    public void Add(EventHandler handler) => _handlers.Add(new WeakReference(handler.Target));
    public void Remove(EventHandler handler) { /* 略 */ }
    public void Raise(object sender, EventArgs e)
    {
        var alive = new List();
        foreach (var wr in _handlers)
            if (wr.Target is EventHandler h) h(sender, e); else alive.Add(wr);
        _handlers.Clear(); _handlers.AddRange(alive);
    }
}

優點:訂閱者無需實作 IDisposable,生命週期完全由 GC 掌控。缺點:Raise 需遍歷清理失效引用,高頻事件有效能開銷。

最佳實踐建議

  • **短生命週期物件訂閱長生命週期事件(靜態/單例)必須解除**;
  • 習慣在 Dispose/Unloaded/OnNavigatedFrom 中 -=;
  • 框架層面可提供 WeakEventManager(WPF)或自訂 WeakEvent 基礎設施;
  • 記憶體分析器(dotMemory、PerfView)定期檢查事件委托保留鏈。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved