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

WPF 框架記憶體洩漏 Bug:DeferredResourceReference 與 ResourceReferenceExpression 強參考循環

     編輯:關於.NET

專案中某 WPF 模組操作過程中 **記憶體單增不減**,使用 **ANTS Memory Profiler 5** 分析發現根因:Application.Resources 永久強參考臨時控制項 SelectionDataGrid,導致整個視覺樹無法被 GC 回收。

洩漏物件參考鏈 (Profiler 截圖)

洩漏物件引用圖

參考路徑

Application.Resources (靜態資源字典)
  → DeferredResourceReference (延遲資源參考)
    → Inflated 事件 (強參考)
      → ResourceReferenceExpression (資源參考運算式)
        → SelectionDataGrid (臨時控制項實例)
          → 整個視覺樹...

官方 Bug 說明 (Microsoft Connect / KB)

When a DeferredResourceReference reference is used by a ResourceReferenceExpression class, the internal Inflated event causes a strong reference to be held to the expression. This keeps the whole visual tree from being collected by the Garbage Collector after the references are removed from the tree if the DeferredResourceReference reference is never inflated.

通俗解釋

  1. DynamicResource 或 {DynamicResource ...} 建立 ResourceReferenceExpression 監聽資源變更。
  2. 資源字典回傳 DeferredResourceReference (延遲載入包裝器)。
  3. DeferredResourceReference 內部 Inflated 事件 **強參考** 註冊的 ResourceReferenceExpression。
  4. 若資源 **從未被實際存取 (Inflate)**,此事件永不觸發移除,**強參考鏈永久存在**。
  5. 臨時控制項 (如 DataGrid 的 Cell/Row 模板內元素) 被表達式參考 → **視覺樹無法釋放** → 記憶體洩漏。

影響範圍與觸發條件

  • WPF 3.5 SP1 / 4.0 / 4.5 均受影響
  • 使用 DynamicResource 參考 **非共享、短生命週期** 資源 (如 DataTemplate 內動態建立的控制項)
  • 資源定義在 Application.Resources 或長生命週期 ResourceDictionary 中

官方補丁狀態

😭 該補丁並不提供下載 —— 僅透過私有支援管道 (PSS) 提供給付費客戶,公開版本 **未整合至任何 Service Pack 或累積更新**。

實務規避方案 (Workarounds)

方案一:改用 StaticResource (最簡單、效能最佳)




    




    

  • StaticResource 在載入時一次性解析,**不建立 ResourceReferenceExpression**,完全避開 Bug。
  • 適用於 **資源生命週期 ≥ 使用者生命週期** 的場景 (90%+ 情況)。

方案二:資源定義就近放置,縮短參考鏈




    

  • 資源隨 Window 釋放,即使洩漏也隨視窗關閉回收,不會累積至進程結束。

方案三:程式碼後置手動清理事件 (針對動態資源)


public partial class MyControl : UserControl
{
    public MyControl()
    {
        InitializeComponent();
        Unloaded += (s, e) =>
        {
            // 強制清除 DynamicResource 產生的表達式
            var expr = this.GetValue(MyPropertyProperty) as ResourceReferenceExpression;
            if (expr != null) expr.Dispose(); // 內部方法,需反射呼叫
        };
    }
}
⚠️ 方案三屬 Hack 手段,依賴內部實作細節,**升級 .NET 版本可能失效**,僅作最後手段。

方案四:升級 .NET 4.6.2+ / .NET Core 3.0+

  • 後續版本 **重寫資源解析管線**,此 Bug 已在新架構中修復 (但官方未回溯修補舊版)。
  • 長期專案建議規劃遷移至 .NET 6+/WPF on .NET Core。

偵測與驗證腳本 (自動化測試)


// 單元測試:驗證控制項 Unloaded 後可被 GC 回收
[TestMethod]
public void Control_Should_Be_Collectable_After_Unload()
{
    var control = new SelectionDataGrid();
    var weakRef = new WeakReference(control);
    control = null;
    GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
    Assert.IsFalse(weakRef.IsAlive, "控制項應可被回收,否則存在記憶體洩漏");
}

總結

WPF DeferredResourceReference.Inflated 事件強參考 ResourceReferenceExpression 是 **經典框架級記憶體洩漏**,微軟未公開修補。**核心規避原則:能用 StaticResource 絕不用 DynamicResource;動態資源定義在短生命週期字典中;長期專案規劃遷移至 .NET 6+**。定期以 Memory Profiler 掃描 Application.Resources 強參考鏈,可及早發現同類洩漏。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved