WPF MVVM中订阅窗口PreviewKeyUp/PreviewKeyDown事件是否会引发内存泄漏?
WPF 订阅父窗口键盘事件内存泄漏问题解答
核心结论
- 该写法存在明确的内存泄漏风险,GC 不会自动回收被订阅引用锁住的 UserControl 实例
- 必须在
Unloaded事件中手动取消事件订阅,避免内存持续占用
原因说明
WPF 事件订阅的本质是发布者持有订阅者的强引用:
你代码中的发布者是 Window 实例,订阅者是当前 UserControl1 实例。只要 Window 没有被销毁回收,它就会一直持有 UserControl1 的引用,哪怕你把 UserControl1 从视觉树中完全移除、业务层也不再持有该 UserControl 的引用,GC 也无法回收这个 UserControl 的内存,最终随着动态加载 UserControl 的次数变多,内存占用会持续上涨。
正确实现方案
- 首先在 Unloaded 事件中添加退订逻辑,注意要做空值判断,避免 UserControl 脱离视觉树后无法获取到 Window 实例的异常:
private void UserControl1_Unloaded(object sender, RoutedEventArgs e) { var window = Window.GetWindow(this); if (window != null) { window.PreviewKeyUp -= UserControl1_PreviewKeyUp; window.PreviewKeyDown -= UserControl1_PreviewKeyDown; } }
- 可选优化:由于 Loaded、Unloaded 事件可能因为 UserControl 的重复挂载/卸载触发多次,建议增加标志位避免重复订阅:
private bool _isKeyEventSubscribed = false; private void UserControl1_Loaded(object sender, RoutedEventArgs e) { if (_isKeyEventSubscribed) return; var window = Window.GetWindow(this); if (window == null) return; window.PreviewKeyUp += UserControl1_PreviewKeyUp; window.PreviewKeyDown += UserControl1_PreviewKeyDown; _isKeyEventSubscribed = true; } private void UserControl1_Unloaded(object sender, RoutedEventArgs e) { if (!_isKeyEventSubscribed) return; var window = Window.GetWindow(this); if (window != null) { window.PreviewKeyUp -= UserControl1_PreviewKeyUp; window.PreviewKeyDown -= UserControl1_PreviewKeyDown; } _isKeyEventSubscribed = false; }
内容的提问来源于stack exchange,提问作者Melissa
相关产品推荐
相关产品推荐

