You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 16:25:09