Windows 睡眠/恢复后 WPF Windows 无响应

Posted

技术标签:

【中文标题】Windows 睡眠/恢复后 WPF Windows 无响应【英文标题】:WPF Windows Not Responding After Windows Sleep/Resume 【发布时间】:2021-12-08 11:09:48 【问题描述】:

我有一个使用 .NET 5 的相当简单的 C# WPF 应用程序。基本上它位于后台并为最终用户计时特定事件。这些事件是从外部生成的 xml 文件构建的。

该应用程序由 2 个窗口组成,一个隐藏的窗口负责执行所有操作。如果它检测到某个事件到期,它会引发一条 toast 消息,单击该消息会打开另一个窗口以向用户显示事件详细信息。一切正常并按预期运行,除非在 Windows 睡眠/暂停和恢复之后。我们显然不希望事件在睡眠/暂停时累加,因此我们关闭隐藏窗口并在恢复时再次打开它。那里没有问题,但是一旦系统恢复并引发事件,可见窗口将拒绝显示。如果在睡眠/挂起发生时可见窗口是打开的,那么在恢复时整个窗口被冻结并拒绝响应(关闭窗口的唯一方法是杀死应用程序并重新启动)

APP代码如下:-

public static Forms.NotifyIcon notifyIcon;
public static MainWindow mw;
public static ConfigWindow cw;

protected override void OnStartup(StartupEventArgs e)
        
            base.OnStartup(e);
            
            SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
            // Listen to notification activation
            ToastNotificationManagerCompat.OnActivated += toastArgs =>
            
                // Obtain the arguments from the notification
                ToastArguments args = ToastArguments.Parse(toastArgs.Argument);

                // Obtain any user input (text boxes, menu selections) from the notification
                ValueSet userInput = toastArgs.UserInput;

                // Need to dispatch to UI thread if performing UI operations
                    Application.Current.Dispatcher.Invoke(delegate
                    
                        ToastControl.HandleToast(args);
                    );
            ;

            ConfNotifyIcon();
            OpenApp();
        

        private void ConfNotifyIcon()
        
            notifyIcon = new Forms.NotifyIcon();
            notifyIcon.Icon = new System.Drawing.Icon("Images/Wellformation.ico");
            notifyIcon.DoubleClick += OnClick;
            notifyIcon.ContextMenuStrip = new Forms.ContextMenuStrip();
            notifyIcon.ContextMenuStrip.Items.Add("Open", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnClick);
            notifyIcon.ContextMenuStrip.Items.Add("Close", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnClose);
            notifyIcon.ContextMenuStrip.Items.Add(new Forms.ToolStripSeparator());
            notifyIcon.ContextMenuStrip.Items.Add("Exit", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnExit);
        

        private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
        
            switch (e.Mode)
            
                case PowerModes.Suspend:
                    this.Dispatcher.BeginInvoke((Action)(() =>
                    
                        PrepareLock();
                    ), null);
                    break;
                case PowerModes.Resume:
                    this.Dispatcher.BeginInvoke((Action)(() =>
                    
                        PrepareAwake();
                    ), null);
                    break;
                default:
                    break;
            
        

        private void PrepareAwake()
        
            OpenApp();
            ConfNotifyIcon();
            notifyIcon.Visible = true;
        

        private void PrepareLock()
        
            notifyIcon.Dispose();
            cw.Close();
        

        private void OnExit(object sender, EventArgs e)
        
            Application.Current.Shutdown();
        

        private void OnClose(object sender, EventArgs e)
        
            mw.Close();
        

        private void OnClick(object sender, EventArgs e)
        
            OpenMain();
        

        private void OpenMain()
        
            mw = new();
            mw.Show();
            mw.Activate();
        

public static void OpenApp()
        
            cw = new ConfigWindow();
        

隐藏的Window XAML如下:-

<Window x:Class="WellformationDesktopApplication.ConfigWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WellformationDesktopApplication"
        mc:Ignorable="d"
        Title="ConfigWindow" Height="1" Width="1" Visibility="Hidden" WindowState="Minimized">
    <Grid>
        
    </Grid>
</Window>

代码如下:-

Timer at = new();

        public ConfigWindow()
        
            BuildConfig();
            InitializeComponent();
        

        public void refreshconfig()
        
            myObjects.Clear();
            myObjects = NudgeManager.GetNudges();
            NudgeHandler(myObjects);
        

        public void BuildConfig()
        
            myObjects.Clear();
            myObjects = GetEvents(); // pulls a list of event names with intervals from the config file
            EventHandler(myObjects); //Goes through the list of events and figures out when the next event is due based upon the interval in the configuration
            ActionTimer();
        

        
        private void ActionTimer()
        
            at.Interval = 60000;
            at.Elapsed += ChecktActions;
            at.AutoReset = true;
            at.Enabled = true;
        

        private void ChecktActions(object sender, ElapsedEventArgs e)
        
            //Go through the trigger times for all events and see if those time have passed, if they have raise a toast showing the event name.
            //If an event is raised reset the trigger time for the event based upon the interval and reset that time.
            
            

可见窗口XAML如下:-

<Window x:Class="WellformationDesktopApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WellformationDesktopApplication"
        mc:Ignorable="d"
        ResizeMode="NoResize"
        WindowStyle="None"
        Title="MainWindow" Height="500" Width="800" Background="x:Null" Foreground="x:Null" AllowsTransparency="True">

    <Grid x:Name="BG">
        <TextBlock x:Name="Display" HorizontalAlignment="Left" Margin="546,13,0,0" Text="Show event name and appropriate information about the event here..." VerticalAlignment="Top" FontSize="22"/>

    </Grid>
</Window>

代码如下:-

public MainWindow()
        
            InitializeComponent();
            setstyles();
            this.MouseLeftButtonDown += delegate  DragMove(); ;
        

我们知道与 ConfigWindow 相关的一切工作正常,我们知道它在挂起时关闭,并在恢复时打开一个新的窗口,设置新的时间并且所有适当的警报都工作。

问题在于 MainWindow,因为在暂停和恢复之后,它根本无法交互。图标上的打开按钮什么也不做,如果打开的窗口完全冻结并且无法以任何方式进行交互,并且如果在窗口上单击 toast 则不会打开但其余的 toast 处理代码可以正常工作它。这发生在 Win8、Win10 和 Win11 上。

任何帮助,因为我完全不知道这是怎么发生的?

谢谢

【问题讨论】:

在启动过程中,您首先调用ConfNotifyIcon(),然后调用OpenApp()。恢复后,您执行相反的操作。 ConfigWindow 中的任何内容是否依赖于该图标(是否希望首先创建它)? 另外,从AutoReset 属性,我们可以推断您使用的是System.Timers.Timer 而不是WPF DispatcherTimer。这些计时器don't tick when the system is asleep,因此您似乎正在尝试围绕一个不存在的问题进行编程。 还要注意Timer.SynchronizingObject Property 特别是"当SynchronizingObject 为null 时,处理Elapsed 事件的方法会在系统线程池中的一个线程上调用。" 我们看不到 ChecktActions 里面的内容,但如果你在这里操作 UI,这可能会有问题。 回答之前的cmets。 ConfigWindow 和 NotifyIcon 之间没有链接。它们是没有代码连接的独立实体。暂停后重置计时器的原因是事件的计时基于机器的连续使用。完整的要求有点复杂,但最初的程序员这样做是有原因的。 ChecktActions 中唯一发生的事情是将 datetime now 与时间列表进行比较,如果事件的时间已经过去,则引发 Toast Notifications。没有进行任何 UI 操作。 【参考方案1】:

经过大量工作并逐节浏览代码,将其注释掉,看看它是否有所作为,我发现了问题。

隐藏在隐藏窗口的代码深处(下一行调用了 4 次函数)我发现 EventHandler() 也为

SystemEvents.SessionSwitch += new SessionSwitchEventHandler(OnSessionSwitch);

所有相关函数都隐藏在一个单独的类中,该类不是从窗口本身直接引用的。

当此行被注释掉时,一切正常,在暂停/恢复窗口后将其放入隐藏窗口并附加到隐藏窗口,整个代码中不会发生 UI 更改(因此隐藏窗口继续完全正常工作)不与 UI 交互)。

通过将此代码提升到 APP 空间并在那里处理而不是在窗口中处理,问题就消失了(尽管已经发现了其他在 Windows 恢复时未处理的问题,我现在必须修复)。

所以答案是,对于任何类型的 SystemEvents 的 WPF 应用程序侦听器都需要位于 APP 代码空间中,而不是在窗口中。

【讨论】:

以上是关于Windows 睡眠/恢复后 WPF Windows 无响应的主要内容,如果未能解决你的问题,请参考以下文章

电脑的待机,休眠,和睡眠有啥区别,哪个更省电

CCRepeatForever 不会在睡眠后立即恢复

wpf 多语言对应 切换了 dll后 如何刷新窗体(window)

WSL2 时钟与 Windows 不同步

wpf中从windows中打开windows1但为啥不显示windows1中的控件

从 WPF 窗口获取 System.Windows.Forms.IWin32Window