WPF中使用SetWaitableTimer实现定时休眠唤醒失败问题排查
WPF应用休眠后无法按时唤醒问题排查与解决
问题描述
开发WPF应用时,需实现设备在指定时间休眠并唤醒的功能。使用SetWaitableTimer实现后,设备能正常进入休眠状态,但无法按时唤醒,测试时设置的唤醒间隔为1分钟。
现有代码
DateTime dateTime = DateTime.Now; var dt = dateTime.AddMinutes(1); Log.Info("WakeUp after " + dt.ToString()); Utilities.TaskScheduler.SetWakeAt(dt); Utilities.TaskScheduler.Sleep(); public delegate void TimerCompleteDelegate(); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName); [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetWaitableTimer(IntPtr hTimer, [In] ref long pDueTime, int lPeriod, TimerCompleteDelegate pfnCompletionRoutine, IntPtr pArgToCompletionRoutine, bool fResume); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CancelWaitableTimer(IntPtr hTimer); [DllImport("powrprof.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent); public static bool Hibernate() { return SetSuspendState(true, false, false); } public static bool Sleep() { return SetSuspendState(false, false, false); } public static IntPtr SetWakeAt(DateTime dt) { TimerCompleteDelegate timerComplete = null; // read the manual for SetWaitableTimer to understand how this number is interpreted. long interval = dt.ToFileTimeUtc(); IntPtr handle = CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer"); SetWaitableTimer(handle, ref interval, 0, timerComplete, IntPtr.Zero, true); return handle; }
问题原因与修复方案
1. 修正SetWaitableTimer时间格式
SetWaitableTimer的pDueTime参数规则:正数表示相对时间,负数表示绝对时间。你当前直接使用dt.ToFileTimeUtc()的正数结果,导致定时器无法正确识别唤醒时间,需将绝对时间转为负数。
修复后代码:
public static IntPtr SetWakeAt(DateTime dt) { TimerCompleteDelegate timerComplete = null; // 将绝对时间转换为负数,符合SetWaitableTimer的参数要求 long interval = -dt.ToFileTimeUtc(); IntPtr handle = CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer"); // 添加错误日志,排查调用失败情况 if (!SetWaitableTimer(handle, ref interval, 0, timerComplete, IntPtr.Zero, true)) { int errorCode = Marshal.GetLastWin32Error(); Log.Error($"SetWaitableTimer调用失败,错误码:{errorCode}"); } return handle; }
2. 保持定时器句柄引用
创建的定时器句柄若被GC回收,会导致定时器失效。需在类级别保留句柄引用,直到唤醒完成或主动取消:
// 在TaskScheduler类中添加静态字段保存句柄 private static IntPtr _wakeTimerHandle = IntPtr.Zero; public static IntPtr SetWakeAt(DateTime dt) { // 先取消之前的定时器(如果存在) if (_wakeTimerHandle != IntPtr.Zero) { CancelWaitableTimer(_wakeTimerHandle); _wakeTimerHandle = IntPtr.Zero; } TimerCompleteDelegate timerComplete = null; long interval = -dt.ToFileTimeUtc(); _wakeTimerHandle = CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer"); if (!SetWaitableTimer(_wakeTimerHandle, ref interval, 0, timerComplete, IntPtr.Zero, true)) { int errorCode = Marshal.GetLastWin32Error(); Log.Error($"SetWaitableTimer调用失败,错误码:{errorCode}"); } return _wakeTimerHandle; } // 添加清理方法,在应用退出或无需定时器时调用 public static void CleanupWakeTimer() { if (_wakeTimerHandle != IntPtr.Zero) { CancelWaitableTimer(_wakeTimerHandle); _wakeTimerHandle = IntPtr.Zero; } }
3. 系统权限与电源设置检查
- 确保应用以管理员权限运行,设置唤醒定时器需要系统级权限;
- 检查系统电源选项:在「电源选项 -> 更改计划设置 -> 更改高级电源设置 -> 睡眠 -> 允许唤醒计时器」中,确保功能已开启;
- 部分硬件(如笔记本)可能在BIOS/UEFI中禁用了定时器唤醒,需检查硬件层面设置。
4. 确认SetSuspendState参数
你的Sleep()方法中SetSuspendState(false, false, false)的第三个参数disableWakeEvent设为false是正确的,该参数表示不禁止唤醒事件,确保定时器可以唤醒系统。
内容的提问来源于stack exchange,提问作者Nation
相关产品推荐
相关产品推荐

