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 无响应的主要内容,如果未能解决你的问题,请参考以下文章
wpf 多语言对应 切换了 dll后 如何刷新窗体(window)