AgentSkillsCN

optimizing-wpf-memory

涵盖 WPF 内存优化,包括 Freezable 模式、常见内存泄漏成因,以及诊断技巧。在遇到内存增长问题、实施资源缓存,或调试内存相关问题时使用。

SKILL.md
--- frontmatter
name: optimizing-wpf-memory
description: Covers WPF memory optimization including Freezable patterns, common memory leak causes, and diagnostic techniques. Use when experiencing memory growth, implementing resource caching, or debugging memory issues.

WPF Memory Optimization

1. Freezable Pattern

Why Freeze?

BenefitDescription
Thread-safeCan be used across threads
No change trackingReduces overhead
Renderer optimizationBetter GPU utilization

Basic Usage

csharp
// Always freeze static resources
var brush = new SolidColorBrush(Colors.Red);
brush.Freeze();

var pen = new Pen(Brushes.Black, 1);
pen.Freeze();

XAML Freeze

xml
<Window xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
        mc:Ignorable="po">
    <Window.Resources>
        <SolidColorBrush x:Key="FrozenBrush" Color="Red" po:Freeze="True"/>
    </Window.Resources>
</Window>

When Freeze Fails

csharp
if (brush.CanFreeze)
    brush.Freeze();
else
    // Has bindings or animations - cannot freeze

Modifying Frozen Objects

csharp
var clone = frozenBrush.Clone(); // Creates unfrozen copy
clone.Color = Colors.Blue;
clone.Freeze(); // Freeze again if needed

2. Common Memory Leaks

Event Handler Leaks

csharp
// ❌ LEAK: Static event holds reference
SomeStaticClass.StaticEvent += OnEvent;

// ✅ FIX: Unsubscribe in Unloaded
Unloaded += (s, e) => SomeStaticClass.StaticEvent -= OnEvent;

CompositionTarget.Rendering Leak

csharp
// ❌ LEAK: Never unsubscribed
CompositionTarget.Rendering += OnRendering;

// ✅ FIX: Always unsubscribe
Loaded += (s, e) => CompositionTarget.Rendering += OnRendering;
Unloaded += (s, e) => CompositionTarget.Rendering -= OnRendering;

Binding Without INotifyPropertyChanged

csharp
// ❌ LEAK: PropertyDescriptor retained
public string Name { get; set; } // No INPC

// ✅ FIX: Implement INPC
public string Name
{
    get => _name;
    set { _name = value; OnPropertyChanged(); }
}

DispatcherTimer Leak

csharp
// ❌ LEAK: Timer keeps running
_timer = new DispatcherTimer();
_timer.Tick += OnTick;
_timer.Start();

// ✅ FIX: Stop and cleanup
Unloaded += (s, e) =>
{
    _timer.Stop();
    _timer.Tick -= OnTick;
};

Image Resource Leak

csharp
// ❌ LEAK: Stream kept open
var bitmap = new BitmapImage(new Uri(path));

// ✅ FIX: Load immediately and release stream
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(path);
bitmap.EndInit();
bitmap.Freeze();

3. Diagnostic Checklist

Code Review Points

  • All static event subscriptions have unsubscribe logic
  • CompositionTarget.Rendering unsubscribed in Unloaded
  • DispatcherTimer stopped in Unloaded
  • ViewModels implement INotifyPropertyChanged
  • Images use BitmapCacheOption.OnLoad
  • Static resources are Frozen

Diagnostic Tools

ToolPurpose
VS Diagnostic ToolsReal-time memory snapshots
dotMemoryDetailed retention paths
PerfViewGC and allocation analysis

Memory Monitor

csharp
public static class MemoryMonitor
{
    public static void LogMemory(string context)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var mem = GC.GetTotalMemory(true) / 1024.0 / 1024.0;
        Debug.WriteLine($"[{context}] Memory: {mem:F2} MB");
    }
}

4. Resource Factory Pattern

csharp
public static class FrozenResources
{
    public static SolidColorBrush CreateBrush(Color color)
    {
        var brush = new SolidColorBrush(color);
        brush.Freeze();
        return brush;
    }

    public static Pen CreatePen(Color color, double thickness)
    {
        var pen = new Pen(CreateBrush(color), thickness);
        pen.Freeze();
        return pen;
    }
}