WPF中NavigationWindow内Page关闭时Application.Current返回null的问题解决
解决WPF Page关闭时Dispatcher调用引发的NullReference异常
这个问题我之前做WPF导航应用时也碰到过,核心原因是ViewModel中的事件订阅没有及时取消,导致Page已经卸载(或Application开始关闭)后,ClientProxy.ImageUpdate事件还在触发,此时Application.Current已经被清理为null,进而调用Dispatcher.Invoke时抛出异常。而且事件触发的时机可能比你的清理代码更早,所以即使调用了releaseServerViewModel也来不及阻止。
一步步解决问题:
1. 给ViewModel添加可控制的事件订阅与清理逻辑
首先把ViewModel中匿名的事件处理器改成命名方法(方便取消订阅),同时添加状态标志标记ViewModel是否正在关闭,避免后续事件触发无效操作。另外,用Page的Dispatcher替代Application.Current.Dispatcher,减少对全局Application上下文的依赖。
修改后的ViewModel代码:
public class CompactLayoutViewModel { private bool _isClosing; private readonly Dispatcher _pageDispatcher; // 构造时传入Page的Dispatcher,避免依赖Application上下文 public CompactLayoutViewModel(Dispatcher pageDispatcher) { _pageDispatcher = pageDispatcher; // 用命名方法订阅事件,方便后续取消 ClientProxy.ImageUpdate += OnImageUpdate; } private async void OnImageUpdate(object sender, ImageUpdateEventArgs args) { // 如果已经在关闭流程中,直接返回,不执行后续操作 if (_isClosing) return; await ImageUpdateAsync(args.image, args.fps); } private Task ImageUpdateAsync(byte[] img, int fps) { if (_isClosing) return Task.CompletedTask; // 先检查Dispatcher是否可用,避免访问null if (_pageDispatcher == null || _pageDispatcher.HasShutdownStarted) { return Task.CompletedTask; } try { if (_pageDispatcher.CheckAccess()) { // 当前就在Dispatcher线程,直接执行操作 UpdateImageLogic(img, fps); } else { // 切换到Dispatcher线程执行 _pageDispatcher.Invoke(() => UpdateImageLogic(img, fps)); } } catch (Exception ex) { // 异常处理逻辑 } return Task.CompletedTask; } // 把原来的操作逻辑抽成单独方法,代码更清晰 private void UpdateImageLogic(byte[] img, int fps) { // 执行你的图片更新操作 } public void ReleaseResources() { // 标记为关闭状态,阻止后续事件执行 _isClosing = true; // 取消事件订阅,从根源阻止新的事件触发 ClientProxy.ImageUpdate -= OnImageUpdate; // 执行原来的releaseServerViewModel逻辑 releaseServerViewModel(); } }
2. 在Page的Unloaded事件中触发ViewModel清理
使用Page的Unloaded事件(而非Application的ShutdownStarted),因为它是Page级别的事件,触发时机更早,此时Application上下文还未被清理。同时添加一个标志确保清理逻辑只执行一次(避免Unloaded多次触发)。
修改后的Page代码:
public partial class CompactLayout : Page { private readonly CompactLayoutViewModel _viewModel; private bool _hasUnloaded; // 标记是否已经执行过清理 public CompactLayout() { InitializeComponent(); // 传入当前Page的Dispatcher给ViewModel DataContext = _viewModel = new CompactLayoutViewModel(this.Dispatcher); this.Unloaded += OnPageUnloaded; } private void OnPageUnloaded(object sender, RoutedEventArgs e) { if (_hasUnloaded) return; _hasUnloaded = true; // 触发ViewModel的资源清理 _viewModel.ReleaseResources(); // 取消事件订阅,避免内存泄漏 this.Unloaded -= OnPageUnloaded; } }
关键细节说明:
- 取消事件订阅:这是最核心的一步,只有取消
ClientProxy.ImageUpdate的订阅,才能彻底阻止后续事件触发,从根源避免无效的Dispatcher调用。 - 使用Page的Dispatcher:相比
Application.Current.Dispatcher,Page自身的Dispatcher更可靠,只要Page还未被完全销毁,它的Dispatcher就会存在,不会因为Application开始关闭而变成null。 - 状态标志检查:即使取消订阅前还有事件在排队执行,状态标志
_isClosing也会让这些排队的操作直接返回,不会执行到Dispatcher调用的逻辑。
内容的提问来源于stack exchange,提问作者user2952272




