如何正确使用SetThreadExecutionState来阻止Windows进入睡眠

最近产品有个需求,需要在升级的时候阻止Windows系统进入自动睡眠。需求到手后,小搜了一下,搜到SetThreadExecutionState这个函数,相关的博客挺多,官方文档也挺清晰,想必应该是手拿把掐了,结果没想到连续踩了好几个坑。现在,我就把SetThreadExecutionState的基本使用方法和我踩过的坑整理出来分享给大家。

函数原型

复制代码
EXECUTION_STATE SetThreadExecutionState(
  [in] EXECUTION_STATE esFlags
);

上面就是SetThreadExecutionState的函数原型,在Win32Api里面算是非常简单的函数原型了。入参和返回值都是EXECUTION_STATE类型,入参表示要设置的状态,返回值表示设置前的原有状态,如果失败返回就是0。

EXECUTION_STATE可以是上面这些值或者它们的组合值。从上表可以看出ES_USER_PRESENT不可用,ES_CONTINUOUS用来表示此次设置的状态是否保持有效,所以实际能够设置的状态有三种,下面简单介绍一下每一种的作用。

ES_SYSTEM_REQUIRED (0x00000001)

ES_SYSTEM_REQUIRED的作用是重置系统空闲计时器。系统空闲计时器,或者叫睡眠空闲超时,就是在持续一定时间没有用户输入时,使系统自动进入睡眠或新式待机状态。在Windows11的"系统-电源和电池-使我的设备在以下时间后进入睡眠状态"中可以设置超时时间。

举个例子,假如我设置了在1分钟后使系统进入睡眠,然后我在运行以下代码后就不再对系统进入任何操作

复制代码
await Task.Delay(30*1000);
SetThreadExecutionState(ES_SYSTEM_REQUIRED);

那么,原本系统会在1分钟后进入睡眠,但是代码在30秒的时候重置了系统空闲计时器,所以实际上系统会在1分30秒后进入睡眠。

如果和ES_CONTINUOUS一起使用,那么会永远阻止系统进入睡眠

复制代码
SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS);

取消的方法是,再调用一次SetThreadExecutionState,设置ES_CONTINUOUS并且不设置ES_SYSTEM_REQUIRED,即

复制代码
SetThreadExecutionState(ES_CONTINUOUS);

ES_DISPLAY_REQUIRED (0x00000002)

ES_DISPLAY_REQUIRED的作用是重置显示空闲计时器。显示空闲计时器,或者叫显示空闲超时,对应的是Windows11的"系统-电源和电池-在此时间后关闭我的屏幕"设置。ES_SYSTEM_REQUIRED的使用方式和ES_SYSTEM_REQUIRED相同,不再举例。

那么有一个问题,假如我把睡眠空闲超时和显示空闲超时都设置为1分钟,显然,在系统空闲1分钟后,屏幕会关闭,系统也会进入睡眠。

如果我的应用程序在30秒的时候,仅重置了睡眠空闲超时,那么1分钟后屏幕会关闭,但系统会继续运行,到1分30秒的时候进入睡眠。

如果我的应用程序在30秒的时候,仅重置了显示空闲超时会怎么样呢?我在Win11 24H2在做了验证,结果是在1分钟的时候,屏幕没有关闭,系统也没有进入睡眠,在1分30秒时,屏幕关闭和系统睡眠同时发生。可见,在Win11 24H2中,不存在一种系统睡眠而屏幕未关闭的状态,系统需要关闭屏幕后才能进入睡眠。

ES_AWAYMODE_REQUIRED (0x00000040)

使用ES_SYSTEM_REQUIRED可以防止系统自动进入睡眠,那如果用户手动点击睡眠,或者其他应用程序调用API使系统进入睡眠,有没有办法阻止这一行为呢?

我们知道,当我们点击开始菜单的睡眠按钮时,屏幕会关闭,系统会进入睡眠,应用程序会被挂起,例如,使用下面的代码,每隔1秒钟,打印一次当前时间

复制代码
while(true)
{
    Console.WriteLine($"{DateTime.Now:T}");
    await Task.Delay(1000);
}

在代码运行中,点击系统开始菜单的睡眠,过一会儿再唤醒系统,可以看到,代码的输出结果为:

复制代码
//......
12:39:24
12:39:25
12:39:36
12:39:37
//......

可以看到中间有10几秒的间隔没有输出,这说明系统睡眠时代码被挂起了,系统唤醒后代码才继续执行。

要想我们的代码能够在系统睡眠时在后台继续执行,可以使用ES_AWAYMODE_REQUIRED。如果我们把代码改成这样,然后执行相同的操作,那么我们仍然会得到时间连续的输出,因为我们的代码没有被挂起,而是在后台继续执行。

复制代码
SetThreadExecutionState(ES_AWAYMODE_REQUIRED | ES_CONTINUOUS);
while(true)
{
    Console.WriteLine($"{DateTime.Now:T}");
    await Task.Delay(1000);
}

ES_AWAYMODE_REQUIRED和ES_SYSTEM_REQUIRED的区别是,ES_AWAYMODE_REQUIRED不会阻止系统进入睡眠,但系统也没有完全睡眠,因为我们的代码仍在运行。在这个状态中,其他没有设置ES_AWAYMODE_REQUIRED的应用会被系统挂起。

小节

  • ES_SYSTEM_REQUIRED用来阻住自动睡眠,ES_DISPLAY_REQUIRED用来阻止屏幕自动关闭,ES_AWAYMODE_REQUIRED使应用程序在睡眠时能够在后台执行。
  • 不带ES_CONTINUOUS是单次请求,重置一次超时计时;带上ES_CONTINUOUS是持续请求,单独设置ES_CONTINUOUS可取消持续请求。
  • ES_AWAYMODE_REQUIRED只适用持续请求。

在多线程中的使用

虽然站在用户的角度来看,是应用程序阻止了睡眠,但SetThreadExecutionState这个函数,如它的函数名称一样,它设置的是线程的执行请求(电源请求)。

进程的电源请求状态,是所有线程的电源请求状态的并集。下面的代码在三个线程中分别设置了ES_DISPLAY_REQUIRED、ES_SYSTEM_REQUIRED和ES_AWAYMODE_REQUIRED三种持续的电源请求:

复制代码
var starter1 = new ThreadStart(()=>
{
    SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
    Thread.Sleep(30000);
});

var starter2 = new ThreadStart(()=>
{
    SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
    Thread.Sleep(30000);
});

var starter3 = new ThreadStart(()=>
{
    SetThreadExecutionState(EXECUTION_STATE.ES_AWAYMODE_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
    Thread.Sleep(30000);
});

var t1 = new Thread(starter1);
t1.Start();

var t2 = new Thread(starter2);
t2.Start();

var t3 = new Thread(starter3);
t3.Start();

运行后,我们在控制台使用powercfg /requests命令来查询当前设备上应用程序的电源请求,结果如下:

结果是AwakeDemo-Console.exe这个进程同时具有三种电源请求。

在一个线程中设置的电源请求,只能在该线程中取消,或者等待线程终止后,电源请求自动取消。

服务进程中无法设置ES_DISPLAY_REQUIRED

话不多说,直接上示例演示:

首先用VS的Windows服务模板新建一个项目

然后在Service1.cs中添加以下代码,使服务在启动时同时设置DISPLAY、SYSTEM和AWAYMODE请求

复制代码
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);

[FlagsAttribute]
public enum EXECUTION_STATE : uint
{
    ES_AWAYMODE_REQUIRED = 0x00000040,
    ES_CONTINUOUS = 0x80000000,
    ES_DISPLAY_REQUIRED = 0x00000002,
    ES_SYSTEM_REQUIRED = 0x00000001
}

protected override void OnStart(string[] args)
{
    SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_AWAYMODE_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
}

编译后找到输出的exe文件,在cmd中输入以下命令创建和运行服务

复制代码
//创建一个名为awake-demo的服务,二进制文件是指定的exe文件的路径
sc create awake-demo binPath= "D:\Projects\CSharp Projects\AwakeDemo\AwakeDemo\bin\Release\AwakeDemo.exe"

//启动awake-demo服务
sc start awake-demo

然后用powercfg /requests查询,结果如下:

可以看到,服务进程只设置了SYSTEM和AWAYMODE请求。通过简单验证可以发现,确实只有SYSTEM和AWAYMODE请求生效了。

Tips

本文展示的结果都来自于支持新式待机(Modern Standby)的移动设备,在不支持新式待机的传统设备上,表现可能会有差异。

相关推荐
怪兽软家6 小时前
DaVinci Resolve/达芬奇 20安装教程及下载
windows·经验分享·生活
chao1898446 小时前
完整MES系统实现 (C# 客户端服务器)
服务器·windows·c#
Hello_Embed7 小时前
Windows 安装 Claude Code 并接入 模型
windows·笔记·ai编程
Muyuan19987 小时前
28.Paper RAG Agent 开发记录:修复 LLM Rerank 的解析、Fallback 与可验证性
linux·人工智能·windows·python·django·fastapi
AxureMost9 小时前
4DDiG DLL Fixe 1.0.8.2 系统DLL修复工具
windows
怣疯knight11 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio
空中海11 小时前
02. 静态逆向、Manifest 分析与 Smali 重打包
服务器·网络·windows
一拳一个娘娘腔12 小时前
告别图形化界面:基于CLI的Windows系统入侵排查与防御实战手册
windows·安全
疋瓞12 小时前
批处理_基础补充、文件和文件夹处理_02
windows
nudt_qxx13 小时前
Ubuntu 24.04/26.04 与 Windows 10/11 双系统时间不同步终极解决方案
windows·stm32·ubuntu