You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动