你是否曾经遇到过这样的问题:WPF应用运行一段时间后,内存占用越来越高,最终导致程序卡顿甚至崩溃?特别是在工业级应用中,这种问题更是致命的。今天我们就来彻底解决这个让无数开发者头疼的内存泄漏难题!
在WPF开发中,最常见的内存泄漏源头就是事件订阅。当你写下这样的代码时:
C#// 危险代码:容易造成内存泄漏
public class DeviceMonitor
{
public DeviceMonitor(DeviceService service)
{
service.DataUpdated += OnDataUpdated; // 强引用陷阱!
}
private void OnDataUpdated(object sender, EventArgs e)
{
// 处理逻辑
}
}
问题核心:即使 DeviceMonitor
对象不再使用,只要 DeviceService
还在运行,它就会持有对 DeviceMonitor
的强引用,导致垃圾回收器无法回收内存。
在工业监控系统中,这种问题尤其严重:
WeakEventManager
是WPF提供的弱事件模式实现,它使用弱引用来订阅事件,避免了强引用导致的内存泄漏。
传统事件订阅 vs WeakEventManager:
C#// ❌ 传统方式:强引用
service.DataUpdated += OnDataUpdated;
// ✅ WeakEventManager:弱引用
DeviceEventManager.AddListener(service, this);
让我们通过一个完整的工业设备监控系统来演示WeakEventManager的强大威力:
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppWpfWeakEventManager.Models
{
public enum DeviceStatus
{
Online,
Offline,
Warning,
Error
}
public class Device : INotifyPropertyChanged
{
private string _name;
private double _temperature;
private DeviceStatus _status;
public event PropertyChangedEventHandler PropertyChanged;
public string Id { get; set; }
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
public double Temperature
{
get => _temperature;
set
{
_temperature = value;
OnPropertyChanged(nameof(Temperature));
// 自动更新状态
if (value > 80)
Status = DeviceStatus.Error;
else if (value > 60)
Status = DeviceStatus.Warning;
else
Status = DeviceStatus.Online;
}
}
public DeviceStatus Status
{
get => _status;
set
{
_status = value;
OnPropertyChanged(nameof(Status));
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// 设备事件参数
public class DeviceEventArgs : EventArgs
{
public Device Device { get; }
public string Message { get; }
public DateTime Timestamp { get; }
public DeviceEventArgs(Device device, string message)
{
Device = device;
Message = message;
Timestamp = DateTime.Now;
}
}
}
这是防泄漏的核心组件:
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AppWpfWeakEventManager.Models;
using System.Windows;
using AppWpfWeakEventManager.Services;
namespace AppWpfWeakEventManager.WeakEventManagers
{
public class DeviceEventManager : WeakEventManager
{
private static DeviceEventManager _currentManager;
private DeviceEventManager()
{
}
public static DeviceEventManager CurrentManager
{
get
{
var managerType = typeof(DeviceEventManager);
var manager = (DeviceEventManager)GetCurrentManager(managerType);
if (manager == null)
{
manager = new DeviceEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}
// 添加监听器
public static void AddListener(DeviceService source, IWeakEventListener listener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
// 移除监听器
public static void RemoveListener(DeviceService source, IWeakEventListener listener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
protected override void StartListening(object source)
{
if (source is DeviceService deviceService)
{
deviceService.DeviceStatusChanged += OnDeviceStatusChanged;
deviceService.TemperatureUpdated += OnTemperatureUpdated;
}
}
protected override void StopListening(object source)
{
if (source is DeviceService deviceService)
{
deviceService.DeviceStatusChanged -= OnDeviceStatusChanged;
deviceService.TemperatureUpdated -= OnTemperatureUpdated;
}
}
private void OnDeviceStatusChanged(object sender, DeviceEventArgs e)
{
DeliverEvent(sender, e);
}
private void OnTemperatureUpdated(object sender, DeviceEventArgs e)
{
DeliverEvent(sender, e);
}
}
}
模拟真实的工业设备数据:
C#using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using AppWpfWeakEventManager.Models;
namespace AppWpfWeakEventManager.Services
{
public class DeviceService
{
private readonly Timer _updateTimer;
private readonly Random _random;
public event EventHandler<DeviceEventArgs> DeviceStatusChanged;
public event EventHandler<DeviceEventArgs> TemperatureUpdated;
public ObservableCollection<Device> Devices { get; }
public DeviceService()
{
_random = new Random();
Devices = new ObservableCollection<Device>();
InitializeDevices();
// 每2秒更新一次温度数据
_updateTimer = new Timer(UpdateDeviceData, null, TimeSpan.Zero, TimeSpan.FromSeconds(2));
}
private void InitializeDevices()
{
Devices.Add(new Device
{
Id = "DEV001",
Name = "压缩机 A1",
Temperature = 45.5,
Status = DeviceStatus.Online
});
Devices.Add(new Device
{
Id = "DEV002",
Name = "冷却塔 B2",
Temperature = 32.1,
Status = DeviceStatus.Online
});
Devices.Add(new Device
{
Id = "DEV003",
Name = "加热炉 C3",
Temperature = 78.9,
Status = DeviceStatus.Warning
});
Devices.Add(new Device
{
Id = "DEV004",
Name = "泵站 D4",
Temperature = 55.2,
Status = DeviceStatus.Online
});
}
private void UpdateDeviceData(object state)
{
Application.Current?.Dispatcher.BeginInvoke(() =>
{
foreach (var device in Devices)
{
var oldTemp = device.Temperature;
var oldStatus = device.Status;
// 模拟温度变化
device.Temperature = Math.Max(20, Math.Min(100,
device.Temperature + (_random.NextDouble() - 0.5) * 10));
// 触发事件
if (Math.Abs(device.Temperature - oldTemp) > 0.1)
{
TemperatureUpdated?.Invoke(this,
new DeviceEventArgs(device, $"温度更新: {device.Temperature:F1}°C"));
}
if (device.Status != oldStatus)
{
DeviceStatusChanged?.Invoke(this,
new DeviceEventArgs(device, $"状态变更: {device.Status}"));
}
}
});
}
public void Dispose()
{
_updateTimer?.Dispose();
}
}
}
实现IWeakEventListener接口是关键:
C#public class MainViewModel : INotifyPropertyChanged, IWeakEventListener
{
private readonly DeviceService _deviceService;
private string _systemStatus;
public ObservableCollection<Device> Devices => _deviceService.Devices;
public ObservableCollection<string> EventLog { get; }
public string SystemStatus
{
get => _systemStatus;
set
{
_systemStatus = value;
OnPropertyChanged(nameof(SystemStatus));
}
}
public MainViewModel()
{
_deviceService = new DeviceService();
EventLog = new ObservableCollection<string>();
// 🎯 关键:使用WeakEventManager订阅事件
DeviceEventManager.AddListener(_deviceService, this);
SystemStatus = "系统运行正常";
}
// 🎯 IWeakEventListener接口实现
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(DeviceEventManager))
{
if (e is DeviceEventArgs deviceEventArgs)
{
HandleDeviceEvent(deviceEventArgs);
return true;
}
}
return false;
}
private void HandleDeviceEvent(DeviceEventArgs e)
{
var logMessage = $"[{e.Timestamp:HH:mm:ss}] {e.Device.Name}: {e.Message}";
Application.Current.Dispatcher.BeginInvoke(() =>
{
EventLog.Insert(0, logMessage);
// 📊 保持日志条数限制
while (EventLog.Count > 50)
{
EventLog.RemoveAt(EventLog.Count - 1);
}
UpdateSystemStatus();
});
}
}
C#// ❌ 错误:没有实现接口
public class MyViewModel : INotifyPropertyChanged
{
// WeakEventManager无法工作
}
// ✅ 正确:必须实现IWeakEventListener
public class MyViewModel : INotifyPropertyChanged, IWeakEventListener
{
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
// 处理弱事件
return true;
}
}
C#// ⚠️ 注意:在非UI线程中更新UI时,必须使用Dispatcher
private void HandleDeviceEvent(DeviceEventArgs e)
{
// ✅ 确保在UI线程中执行
Application.Current.Dispatcher.BeginInvoke(() =>
{
// 更新UI相关的属性
EventLog.Add(e.Message);
});
}
使用以下代码验证内存泄漏是否真正解决:
C#// 内存监控助手
public static class MemoryMonitor
{
public static void LogMemoryUsage(string context)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memoryUsage = GC.GetTotalMemory(false) / 1024 / 1024;
Debug.WriteLine($"[{context}] 内存使用: {memoryUsage} MB");
}
}
让我们看看WeakEventManager的威力:
场景 | 传统事件订阅 | WeakEventManager |
---|---|---|
运行8小时后内存占用 | 🔴 500MB+ | 🟢 80MB |
对象无法回收数量 | 🔴 1000+ | 🟢 0 |
程序稳定性 | 🔴 经常崩溃 | 🟢 持续稳定 |
🔥 实战金句分享:
你在项目中遇到过哪些内存泄漏问题?使用WeakEventManager后有什么心得体会?欢迎在评论区分享你的经验,让我们一起交流学习!
觉得这篇文章对你有帮助的话,请转发给更多需要的同行朋友!让我们一起告别内存泄漏的烦恼! 🚀
相关信息
通过网盘分享的文件:AppWpfWeakEventManager.zip 链接: https://pan.baidu.com/s/1_QH2nohvN5TSwAH4vpHUnQ?pwd=ztwh 提取码: ztwh --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!