更新记录(仅首版,2026年08月22日 发布)
- 首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22176753)
以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。
Go Komura(2026)。《睡眠恢复后就出故障的应用——电源事件机制与扛得住恢复的业务应用设计》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/windows-sleep-resume-power-events/
- DOI(已登记存档)
- 10.5281/zenodo.22176753
- DOI(上次登记版本)
- 10.5281/zenodo.22176754
合上笔记本电脑,第二天早上再打开,业务应用满屏都是错误。设备监控应用只在午休之后丢数据。向 Excel 输出的常驻工具时不时因连接错误而停住。遇到这类症状,首先要怀疑的是跨越睡眠的运行。
有些老的业务应用是按“PC 一直开着”这个前提写的。但如今笔记本电脑已成为业务的主力,放着不动几分钟就会进入睡眠的环境不能再被忽略。而且在支持 Modern Standby(新式待机)的机器上,睡眠的机制本身也与过去不同。
本文面向在 Windows 上开发业务应用和设备控制软件的开发者。按操作系统送来的通知 → 恢复后会坏掉的东西 → 恢复的设计 → 现场调查的顺序,依据一手资料梳理。
1. 先给结论
设计的重心不是“在睡眠之前一定把事情收尾”,而是“无论在哪一刻被停掉,恢复后都能重新立起来”。要抓住的点有 3 个。
- 提前通知并不保证你能把处理做完。睡眠无法拒绝,
PBT_APMSUSPEND给的余地大约只有 2 秒。紧急挂起时连通知本身都不会来。即便是 Modern Standby,也不能假定桌面应用在睡眠期间还能继续运行。123 - 连接、句柄和时间的连续性,都要按“跨不过恢复”的前提来设计。不仅从恢复通知,也要能从通信错误进入同一套重连处理,同时重新审视定时器的调度和经过时间的差值。
- 抑制睡眠和为恢复做准备是两种不同的对策。在必要的处理区间使用
SetThreadExecutionState或电源请求,结束后一定要解除。但它挡不住用户主动执行的睡眠操作,因此不能成为省掉恢复处理的理由。45
也可以按目的从下面的章节读起。
| 想了解的内容 | 阅读章节 |
|---|---|
| 睡眠前后会送来哪些通知 | 第 2 章:电源事件的流程 |
| Modern Standby 上有什么不同 | 第 3 章:系统的运行与应用的暂停 |
| 连接错误和时间偏差为什么会发生 | 第 4 章:常见症状 |
| 重连、时间管理和睡眠抑制怎么实现 | 第 5 章:扛得住恢复的设计 |
| 用户报障该怎么定位 | 第 6 章:powercfg 与事件日志 |
2. 睡眠前后发生了什么——电源事件的流程
操作系统通过 WM_POWERBROADCAST 消息把电源状态的变化通知给应用。2 先记住挂起转换前后会用到的 3 个事件。与 Modern Standby 低功耗空闲状态的差别,在第 3 章补充。
| 事件 | 含义 |
|---|---|
| PBT_APMSUSPEND | 即将进入睡眠(最后一次准备的机会) |
| PBT_APMRESUMEAUTOMATIC | 已经恢复(恢复时必定送达) |
| PBT_APMRESUMESUSPEND | 由用户操作引起的恢复(这一个是有条件的) |
sequenceDiagram
accTitle: 睡眠与恢复的通知流程
accDescr: 睡眠前 PBT_APMSUSPEND 会带着约 2 秒的余地送达,恢复时 PBT_APMRESUMEAUTOMATIC 必定送达,只有在用户操作引起恢复时才会接着送达 PBT_APMRESUMESUSPEND
participant OS as 操作系统
participant A as 应用
OS->>A: PBT_APMSUSPEND(约 2 秒余地)
A->>A: 保存状态、关闭连接
Note over OS: 睡眠(代码不会运行)
OS->>A: PBT_APMRESUMEAUTOMATIC(恢复时送达)
A->>A: 重连、重建状态
OS->>A: PBT_APMRESUMESUSPEND(仅用户恢复时)
A->>A: 屏幕更新等面向用户的处理
图1:通知只有“睡前一句、恢复后一两句”。重建的主角是恢复一侧的处理。
睡眠前的通知只是“来得及就准备”的机会
PBT_APMSUSPEND 是进入睡眠之前的通知。可以做关闭文件、保存状态之类的准备,但有下面 2 个限制。
| 限制 | 对设计的影响 |
|---|---|
| 处理的余地是每个应用大约 2 秒 | 超过这个时间,系统不会等待应用就继续往下走1 |
| 紧急挂起没有提前通知 | 电池电量危急之类的情况下,不做任何准备就停机2 |
因此,“收到这个通知之后一定能保存完”这样的设计不成立。提前通知里只做来得及的准备,恢复的主体放在恢复一侧。
flowchart TB
accTitle: 普通睡眠与紧急挂起的区别
accDescr: 普通的睡眠会在进入前送达 PBT_APMSUSPEND 并给出约 2 秒准备时间,而电池电量危急等原因导致的紧急挂起没有提前通知就停机,因此依赖提前通知的设计不成立
n2["普通的睡眠"] --> pre["PBT_APMSUSPEND(约 2 秒余地)"]
pre --> s1["准备之后停机"]
e2["紧急挂起(电池即将耗尽)"] --> s2["没有提前通知就停机"]
s2 -.-> l2["“通知一定会来”的设计不成立"]
图2:紧急挂起没有预告。所以准备只是“做得到就算赚到”,主体放在恢复一侧。
恢复通知要把机械性的恢复和面向用户的处理分开
从挂起恢复后,首先送达的是 PBT_APMRESUMEAUTOMATIC。此外,如果恢复由电源按钮或按键触发,或者恢复后检测到用户在场,接着还会送来 PBT_APMRESUMESUSPEND。67
另一方面,经由网络的远程唤醒或为维护而进行的无人值守恢复,只会送来 PBT_APMRESUMEAUTOMATIC。重建连接这类必需的恢复处理放在前者,屏幕更新、要求重新登录等面向用户的动作放在后者。6
flowchart TB
accTitle: 恢复两个阶段的处理分工
accDescr: 恢复时送达的 PBT_APMRESUMEAUTOMATIC 里放重连等机械性的重建,只在用户操作时送达的 PBT_APMRESUMESUSPEND 里放屏幕更新和要求重新登录等面向用户的处理
ra["PBT_APMRESUMEAUTOMATIC(恢复时)"] --> m["机械性的重建"]
rs["PBT_APMRESUMESUSPEND(用户恢复时)"] --> u["面向用户的处理"]
m -.-> m1["重连、重新打开句柄"]
u -.-> u1["屏幕更新、要求重新登录"]
图3:无人值守恢复时后者不会来,把必需的重建放在后者就会漏掉。
没有窗口的应用也能收到通知
没有窗口的服务和控制台应用,可以把 RegisterSuspendResumeNotification 配合 DEVICE_NOTIFY_CALLBACK 使用,通过回调收到同样的通知。8
另外,WM_POWERBROADCAST 无法区分低功耗状态是睡眠还是休眠。7 应用一侧要把它当成“停了一次,又回来了”这一共同事件来设计恢复处理。
3. Modern Standby——“睡眠”的含义变了
系统在运行,和应用能运行,是两回事
传统的 S3 睡眠是把整个系统停下来的模型。相比之下,Modern Standby 是关屏之后系统仍然间歇运行、更接近智能手机的模型。
但是,这并不意味着普通的桌面应用也能继续运行。在进入睡眠的第一个阶段,它们就会被 Desktop Activity Moderator(DAM)暂停。3
| 方式 | 系统的运行 | 桌面应用的前提 |
|---|---|---|
| 传统的 S3 睡眠 | 整个系统停止 | 睡眠期间代码不会运行 |
| Modern Standby | 为维持网络连接、接收通知等目的间歇运行 | 被 DAM 暂停,普通代码不会运行 |
能享受到间歇运行好处的,是适配了这套机制的组件。就业务应用的设计而言,两者得到的结论都是“睡眠期间自己的代码不会运行”。3
flowchart TB
accTitle: 传统睡眠与 Modern Standby 的区别
accDescr: 传统的 S3 睡眠会让整个系统停止,而 Modern Standby 在关屏之后系统仍然间歇运行。但桌面应用会被 DAM 暂停,因此两种方式下应用的代码都不会运行
s3["传统 S3 睡眠:整体停止"] --> conc["应用的代码不会运行"]
ms["Modern Standby:系统间歇运行"] --> dam["桌面应用被 DAM 暂停"]
dam --> conc
图4:模型变了,但对桌面应用来说结论是一样的——“睡眠期间动不了”。
不要把恢复通知当成恢复的唯一条件
Modern Standby 进出低功耗空闲状态的时机,未必与传统的挂起转换一致。也会出现通知没有送达、连接却已经坏掉的情况。请把恢复通知看作加快恢复的辅助手段,把从检测通信错误就能重连的路径放在中心位置。具体结构在第 5 章说明。
另外,向低功耗状态的过渡是分阶段的,所以断开和停止的时机不像 S3 那样清晰。用户自己也很难分清是“只是屏幕关了”还是“已经睡眠了”,因此询问症状时要确认是否合上了盖子、放置了多少分钟。
4. 到底什么会坏——常见症状
恢复后的错误,分成连接、设备句柄和时间连续性来看就容易梳理。对于共享资源,还要确认重新认证和重建网络所需的时间。
TCP 连接在收发之前察觉不到已经断开
睡眠期间,对端以及 NAT、防火墙会把这边的沉默当作超时,丢弃连接。但这边的套接字并不知道这件事,于是要等到恢复后收发时才第一次失败。
也有一直停在接收等待、迟迟不报错的情况。这就是需要用保活来确认连接死活的理由。数据库连接和 WebSocket 也可以按同样的图式来理解。
串口和 USB 设备要重新打开句柄
USB 设备在恢复时有时看起来像是被拔下又插回,原先打开的句柄开始返回错误。这是设备控制应用里“只在午休之后出通信错误”的典型模式。
不要按句柄可以一直用下去来设计,而要做成能重新打开设备的结构。重连的设计在串口通信应用的文章里也有讨论。
周期处理、经过时间和定时处理要分开重新审视
与时间有关的问题分为下面 3 类。
| 处理 | 跨越睡眠后会发生什么 | 应对 |
|---|---|---|
| “每 10 秒轮询一次”的周期处理 | 睡眠期间会停下。恢复后是否触发,因 API 和运行环境而异 | 恢复时重新编排调度 |
| 使用与上次时刻之差的计算 | 差值突然变成“8 小时那么多”,平均值和超时判定被破坏 | 对异常大的差值加保护 |
| “每晚 2 点执行”的定时处理 | 那个时刻 PC 在睡眠,就不会执行 | 需要时使用任务计划程序的唤醒功能 |
尤其是周期处理,过期的那份可能在恢复后立刻触发一次,也可能到下一个周期之前什么都不发生。重要的是不要把“没能执行的那部分怎么处理”交给定时器的隐式行为。
flowchart TB
accTitle: 时间连续性被破坏的 3 种形态
accDescr: 周期处理在睡眠期间停止且恢复后的触发因 API 而异,所以要在恢复时重新编排调度,与上次时刻之差在恢复后会变成巨大值所以要加保护,定时处理在机器睡着时不会执行所以要考虑任务计划程序的唤醒设置
t1["周期处理:睡眠期间停止"] -.-> g1["恢复时重新编排调度"]
t2["经过时间的差值:变得巨大"] -.-> g2["对异常差值加保护"]
t3["定时处理:睡着了没执行"] -.-> g3["用唤醒设置把机器叫醒"]
图5:定时器和时刻相关的处理要按“时间会跳”的前提来写。3 种形态各有各的应对套路。
flowchart TB
accTitle: 跨越睡眠会坏掉的 3 样东西
accDescr: 跨越睡眠后,TCP 连接会被对端的超时丢弃,USB 设备的句柄会因被当作重新接入而失效,基于经过时间的处理会观测到巨大的时间跳变。三者分别用重连、重新打开和差值保护来重建
sleep["睡眠区间"] --> tcp["TCP 连接:已被对端丢弃"]
sleep --> usb["USB 设备:句柄失效"]
sleep --> time["经过时间:巨大的跳变"]
tcp -.-> r1["检测错误并重连"]
usb -.-> r2["重新打开设备"]
time -.-> r3["对异常差值加保护"]
图6:会坏的东西分“连接”“句柄”“时间连续性”3 类,各自的重建套路都是定好的。
网络驱动器和 VPN 在恢复之后还有一段等待时间
网络驱动器和 VPN 在恢复后有时需要重新认证、重新建立连接。因此恢复之后的几秒到几十秒里,存在一段访问会失败的时间。
不要在恢复的瞬间一齐重试,而要设计成先稍等一会儿,再分阶段重试。
5. 扛得住恢复的应用怎么做
原则是即使连接和句柄跨不过睡眠,也要做成随时都能重新立起来的结构。重连、时间的处理、必要区间的睡眠抑制,要分别设计。
让恢复通知和通信错误汇入同一套重连处理
在顶层窗口的 WM_POWERBROADCAST 中收到 PBT_APMRESUMEAUTOMATIC 后,丢弃手上持有的连接并重新建立。不过,不能只依赖恢复通知。因为存在漏收通知的情况,也存在通知送达之前就发生的通信。
一定要同时准备“检测到通信错误就重连”的路径,把恢复通知定位成让这段处理提前启动的契机。下面的 C# 示例是从恢复通知发出重连请求的部分。通信错误一侧也要汇入同一套重连处理。
// C#: 让恢复通知和通信错误都汇入同一套重连处理
protected override void WndProc(ref Message m)
{
const int WM_POWERBROADCAST = 0x0218;
const int PBT_APMRESUMEAUTOMATIC = 0x0012;
if (m.Msg == WM_POWERBROADCAST && (int)m.WParam == PBT_APMRESUMEAUTOMATIC)
{
_connectionManager.RequestReconnect(); // 幂等的重连请求
}
base.WndProc(ref m);
}
重连要把“幂等、退避、保活”组合起来
重连处理要凑齐下面 3 点。
| 要素 | 作用 |
|---|---|
| 幂等的重连 | 让它被请求多少次都能安全地重新立起来 |
| 带指数退避的重试 | 失败后拉长到下一次重试的间隔 |
| 稳态下的保活 | 确认连接是否还活着,尽早发现断开 |
这三件套不只对睡眠恢复有用,对网络瞬断和设备重启也能直接派上用场。
flowchart TB
accTitle: 扛得住恢复的重连设计
accDescr: 恢复通知、通信错误和保活失败都汇入同一套幂等的重连处理,失败时按指数退避重试
e1["恢复通知(PBT_APMRESUMEAUTOMATIC)"] --> r["幂等的重连处理"]
e2["检测到通信错误"] --> r
e3["保活失败"] --> r
r --> ok{"成功?"}
ok -->|"是"| run["进入正常运行"]
ok -->|"否"| back["指数退避后重试"]
back --> r
图7:把重连收敛成一条幂等的处理,恢复通知、错误检测、保活无论从哪里进来都走同一条路。
不要把时间跳过去的那段直接混进计算
在使用“距上次的经过时间”的处理里,一旦检测到异常大的差值,就加一道让该区间作废的保护。目的是不把它混进平均值,也不把它当成普通的超时。
在跨越恢复的时间测量中,要区分睡眠期间也在前进的时刻(实际时间)和处理实际消耗的时间。周期处理的调度重排,以及定时处理没能执行时的处理方式,也请按第 4 章的分类重新审视。
不能中断的处理区间要显式抑制睡眠
数据迁移、与设备的连续通信等不希望被睡眠打断的处理期间,使用 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)。如果还想让屏幕保持点亮,就再加上 ES_DISPLAY_REQUIRED。74
另一种手段是用 PowerCreateRequest 和 PowerSetRequest 发出电源请求。它可以附带说明理由的字符串,因此用 powercfg /requests 就能看出“谁、为什么在阻止睡眠”。从运维时能查到理由这一点看,这种方式更友好。5
| 手段 | 管理单位与注意事项 |
|---|---|
SetThreadExecutionState |
以线程为单位。解除要从设置时的同一个线程执行 |
电源请求(PowerCreateRequest + PowerSetRequest) |
用句柄管理。像 async/await 这样线程会切换的处理用这一种 |
不过,设置了抑制,并不等于任何中断都能挡住。下面这些限制要与恢复处理分开确认。
| 限制 | 需要采取的措施 |
|---|---|
| 抑制的只是无操作导致的自动睡眠 | 要为合盖、从开始菜单选择睡眠等显式操作做准备,保留重连处理 |
| 在 Modern Standby 机器上用电池供电时,睡眠超时之后过一段时间电源请求也会被终止 | 不能中断的处理要靠交流供电或运维层面来保障5 |
| 忘记解除会成为 PC 不睡眠的原因 | 处理结束后一定要解除 |
flowchart TB
accTitle: 抑制睡眠的两种手段
accDescr: 无论用随手可用的 SetThreadExecutionState,还是用可以附带理由字符串、管理员能通过 powercfg 看到的电源请求 API,都要在处理结束时一定解除
need["不希望被睡眠打断的处理区间"] --> a["SetThreadExecutionState"]
need --> b["电源请求(PowerSetRequest)"]
a -.-> a1["随手可用、只需指定标志"]
b -.-> b1["带理由、powercfg 可见"]
a --> off["处理结束时一定解除"]
b --> off
图8:两种手段都以“用完就解除”为绝对条件。能把理由可视化的电源请求对运维更友好。
如果持续运行是硬性要求,就重新审视部署位置和运维方式
服务和无窗口应用也可以用 RegisterSuspendResumeNotification 的 DEVICE_NOTIFY_CALLBACK 收到通知。8
但如果真的需要持续运行,就要重新审视“常驻在会睡眠的客户端 PC 上”这件事本身。把处理挪到服务器一侧,或挪到按不睡眠方式运维的终端上,才是根本的对策。
6. 怎么调查——powercfg 与事件日志
接到用户报障时,首先确认“出错之前 PC 是否睡眠过”。问清是否合过盖子、放置了多少分钟,再使用与症状相匹配的工具。
| 想查的内容 | 工具 | 要确认什么 |
|---|---|---|
| 不睡眠的原因 | powercfg /requests |
正在发出电源请求的进程和驱动程序。也能查出忘记解除的 SetThreadExecutionState |
| 自己醒来的原因 | powercfg /lastwake、powercfg /waketimers |
上一次的恢复原因,以及已预约恢复的定时器 |
| Modern Standby 的质量 | powercfg /sleepstudy |
每个睡眠区间的耗电与活动9 |
| 睡眠与恢复的时间线 | 系统事件日志的 Kernel-Power |
进入睡眠和恢复的记录 |
把事件日志与应用自身的日志对照起来,就能客观地确认“出错之前是否发生过恢复”。不要停在怀疑睡眠这一步,要把发生时刻对齐后再做定位。
flowchart TB
accTitle: 电源问题的症状与调查命令的对应
accDescr: 不睡眠的症状用 powercfg /requests 查出电源请求的发起方,自己醒来的症状用 /lastwake 和 /waketimers 查恢复原因,确认时间线则用事件日志的 Kernel-Power
s1["不睡眠"] --> c1["powercfg /requests"]
s2["自己醒来"] --> c2["powercfg /lastwake、/waketimers"]
s3["想确认时间线"] --> c3["事件日志的 Kernel-Power"]
c1 -.-> note["忘记解除的睡眠抑制也能一并查出"]
图9:症状与调查命令的对应分 3 类。先确认“出错之前是否睡眠过”,再区分使用。
7. 小结
睡眠方面的对策,只靠接收通知是做不完的。把“提前准备来不及”和“恢复通知没送达”都算进去,按下面的方式划分职责。
| 设计与调查的对象 | 要抓住的点 |
|---|---|
| 睡眠前 | 无法拒绝。用 PBT_APMSUSPEND 给的约 2 秒余地做准备,但紧急时通知不会来 |
| 恢复通知 | 用 PBT_APMRESUMEAUTOMATIC 做必需的恢复,用 PBT_APMRESUMESUSPEND 做面向用户的处理。不要只依赖通知 |
| 连接与句柄 | 以从通信错误重连为中心,组合幂等性、指数退避和保活 |
| 时间 | 对异常的经过时间差值加保护,重排周期处理。定时处理在机器睡着时不会运行 |
| 睡眠抑制 | 只在必要区间设置,结束后解除。对显式睡眠操作等情况的恢复准备仍要保留 |
| 原因调查 | 询问出错前是否睡眠过,把 powercfg 和 Kernel-Power 的记录与应用日志对照 |
从应用的角度看,睡眠是一个“时间毫无预告地跳过去,与周边的连接断掉之后再回来”的事件。能否把它当作日常的一部分而不是异常状况写进设计,决定了笔记本电脑时代业务应用的稳定性。
相关文章
- 串口通信应用的陷阱 - 涵盖重连与日志设计
- 从应用看到的 Windows 关机——正确扛过退出通知、重启与断电
- Windows 效率模式是什么 - 绿色叶子图标与关闭方法
- 为什么在 Windows 上应优先选择事件等待而非 Sleep(1)
- “无响应”的真正含义——Windows 如何判定应用已挂起,以及不会挂起的设计
相关咨询领域
小村软件有限公司承接“睡眠恢复后通信坏掉”“与设备的连接在午休之后断开”这类故障的原因调查,为既有应用后补重连逻辑与电源事件处理的实现,以及以笔记本电脑运维为前提的业务应用、设备控制软件的设计评审。
参考链接
-
Microsoft Learn, PBT_APMSUSPEND event. 关于这是计算机进入挂起状态之前送达的事件,应用应完成保存数据所需的处理,系统对这条通知的处理只允许大约 2 秒,超过这个时间还在继续处理的应用可能被中断。 ↩ ↩2
-
Microsoft Learn, System Power Management Events. 关于系统会在进入睡眠等工作模式变化之前进行广播,空闲导致的睡眠之前会通知 PBT_APMSUSPEND 以便做关闭文件、保存数据的准备,紧急挂起(电池电量危急等)不会进行提前通知,处理这条消息每个应用最多允许约 2 秒、超时后会被打断,以及恢复时会通知所有应用。 ↩ ↩2 ↩3
-
Microsoft Learn, Prepare software for modern standby. 关于在进入 Modern Standby 的第一个阶段,Desktop Activity Moderator(DAM)会暂停桌面应用,之后系统会分阶段转入低功耗阶段和弹性阶段,只有获准的组件才会间歇运行。 ↩ ↩2 ↩3
-
Microsoft Learn, SetThreadExecutionState function (winbase.h). 关于用 ES_SYSTEM_REQUIRED 和 ES_DISPLAY_REQUIRED 可以抑制系统的空闲睡眠和屏幕关闭,用 ES_CONTINUOUS 声明持续抑制,用完后单独调用 ES_CONTINUOUS 解除的用法。 ↩ ↩2
-
Microsoft Learn, PowerSetRequest function (winbase.h). 关于可以对 PowerCreateRequest 创建的电源请求对象设置维持系统、维持屏幕等请求类型,可以附带用于诊断的理由字符串,以及尚未解除的电源请求可以用 powercfg /requests 列出。 ↩ ↩2 ↩3
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. 关于用户操作引起的恢复或其后检测到用户输入时,它会接在 PBT_APMRESUMEAUTOMATIC 之后送出,远程唤醒等外部因素引起的恢复只送出 PBT_APMRESUMEAUTOMATIC,以及应用应重新打开睡眠时关闭的文件并为用户输入做好准备。 ↩ ↩2
-
Microsoft Learn, WM_POWERBROADCAST message. 关于恢复时必定送出 PBT_APMRESUMEAUTOMATIC、由用户输入引起的恢复还会送出 PBT_APMRESUMESUSPEND,这条消息无法区分低功耗状态的种类,电源状态转换的详情会记录在系统事件日志中,以及要阻止系统进入低功耗状态需调用 SetThreadExecutionState。 ↩ ↩2 ↩3
-
Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). 关于这是注册接收挂起、恢复通知的 API,除了向窗口句柄投递消息之外,指定 DEVICE_NOTIFY_CALLBACK 还能让没有窗口的应用和服务通过回调收到通知。 ↩ ↩2
-
Microsoft Learn, Modern standby SleepStudy. 关于通过 powercfg /sleepstudy 生成的报告,可以确认每个 Modern Standby 区间的耗电、活动和恢复原因(电源按钮、用户输入、唤醒定时器等)。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
DllMain 与加载器锁——“DLL 初始化里什么都别做”的真正原因
为什么不能在 DllMain 里调用 LoadLibrary 或与线程同步。本文依据一手资料,从串行化全部 DLL 通知的加载器锁机制,讲到死锁成立的典型场景、延迟初始化等正确设计,以及挂起的调查步骤。
“无响应”的真相——Windows 如何判定应用卡死,以及不会卡死的设计
Windows 的“无响应”是操作系统在窗口 5 秒未取出消息时作出判定、并换成幽灵窗口的机制。本文讲解判定的内部动作、卡死的常见原因、把繁重处理移出 UI 线程的设计,以及挂起的调查步骤。
解读 Windows 错误码——Win32 错误、HRESULT、NTSTATUS 的三层结构
出现 0x80004005 时,先分解再搜索。讲解 Win32 错误、HRESULT、NTSTATUS 的三层结构,0x8007xxxx=Win32 错误重新封装这一最重要模式,以及用 err.exe 和 PowerShell 查询的方法。
多线程实战最佳实践 C 语言篇——以 Win32 API 的方式安全编写
C 语言 × Win32 的多线程自有定式:用 _beginthreadex 创建线程、SRW 锁与条件变量、Interlocked,以及停止事件 + WaitForMultipleObjects 的停止设计。本文还梳理 TerminateThread 的危险与 DllMa...
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- 应用能提前得知即将睡眠并加以拒绝吗?
- 在目前的 Windows 上,通知可以收到,但无法拒绝。即将进入睡眠时,WM_POWERBROADCAST 消息会送来 PBT_APMSUSPEND 事件,此时可以做关闭文件、保存状态之类的准备,但允许处理的时间大约是每个应用 2 秒,超时后系统不会等待应用就继续往下走。而且在电池即将耗尽等情况下的紧急挂起中,连提前通知本身都不会来。因此“必须在睡眠前做完”的设计不成立,需要做成“无论在哪一刻被切断,恢复时都能重新立起来”的设计。对于实在不希望被睡眠打断的处理区间,用 SetThreadExecutionState 或电源请求(PowerSetRequest)显式抑制。
- 怎样检测机器已经恢复?
- 有窗口的应用就处理 WM_POWERBROADCAST。从挂起恢复时会送来 PBT_APMRESUMEAUTOMATIC,如果恢复由用户操作(电源按钮或按键)引起,之后还会送来 PBT_APMRESUMESUSPEND。无人值守恢复后马上又进入睡眠时只会送来 PBT_APMRESUMEAUTOMATIC,所以基本的分工是:把重连等必需处理放在 PBT_APMRESUMEAUTOMATIC 一侧,把屏幕更新等面向用户的处理放在 PBT_APMRESUMESUSPEND 一侧。没有窗口的服务和控制台应用,把 RegisterSuspendResumeNotification 配合 DEVICE_NOTIFY_CALLBACK 使用,就能通过回调收到同样的通知。
- 睡眠期间能让应用继续运行吗?
- 原则上不能。睡眠期间 CPU 的执行本身就停了(在 Modern Standby 机器上,桌面应用会被 Desktop Activity Moderator 暂停),应用的代码不会运行。有两个选择。一个是只在处理进行期间抑制睡眠。给 SetThreadExecutionState 指定 ES_SYSTEM_REQUIRED,或用 PowerCreateRequest/PowerSetRequest 发出电源请求,就能在这段时间抑制无操作导致的自动睡眠(可以用 powercfg /requests 确认)。但它拦不住用户合盖之类的显式睡眠操作,所以即使在抑制期间也仍需为恢复做准备。另一个是接受睡眠,做成“恢复之后再补上”的设计。夜间批处理这类定时工作,也可以用任务计划程序的“唤醒计算机运行此任务”把 PC 叫醒。真正需要持续运行的处理,正道是挪到配置为不睡眠的服务器或服务上。
- 为什么恢复之后 TCP 连接和串口就用不了了?
- 因为睡眠期间网络适配器和 USB 设备也会落入低功耗状态。TCP 连接已经被对端或 NAT、防火墙的超时丢弃,恢复后的收发会报错(很多时候要到报错才发现)。USB 转串口适配器之类,在恢复时有可能被当作设备拔出再接入,原先打开的句柄失效。两者都应按“句柄和连接跨不过恢复”的前提,实现以恢复通知或通信错误为契机重建连接的重连逻辑。把周期性保活与失败时的指数退避重试组合起来,是成熟的做法。
- 自己进入睡眠、自己醒来的原因该怎么查?
- powercfg 命令是首选工具。“不睡眠”这个方向,用 powercfg /requests 可以列出哪些进程和驱动程序发出了电源请求、在阻止睡眠。“自己醒来”这个方向,用 powercfg /lastwake 可以看到上一次的恢复原因,用 powercfg /waketimers 可以看到已预约恢复的定时器。在 Modern Standby 机器上,powercfg /sleepstudy 会生成睡眠期间耗电与活动的报告。另外,睡眠和恢复的历史会记录在事件日志(系统日志的 Kernel-Power 源)中,所以可以按时间顺序确认“何时进入睡眠、何时因为什么原因醒来”。