從睡眠恢復就壞掉的應用程式 ── 電源事件的機制,以及耐得住恢復的業務應用程式寫法

· · Windows, 電源管理, Windows 開發, 業務應用程式, 裝置控制, 故障調查, Win32 API

更新紀錄(僅初版,2026年08月22日 發布)
初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22176757)

以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。

Go Komura(2026)。〈從睡眠恢復就壞掉的應用程式 ── 電源事件的機制,以及耐得住恢復的業務應用程式寫法〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/windows-sleep-resume-power-events/

DOI(已登錄存檔)
10.5281/zenodo.22176757
DOI(上次登錄版本)
10.5281/zenodo.22176758

闔上筆電,隔天早上打開,業務應用程式滿是錯誤。設備監控應用程式只有午休之後才漏掉資料。輸出到 Excel 的常駐工具,偶爾會因連線錯誤停住。遇到這類症狀,第一個該懷疑的就是跨過睡眠的行為。

過去的業務應用程式中,有些是在「PC 一直開著」這個前提下寫成的。但筆電已成為業務主力的現在,閒置幾分鐘就會睡眠的環境不能再忽略。而且在支援 Modern Standby(新式待命)的機器上,睡眠的機制本身也和過去不同。

本文寫給在 Windows 上開發業務應用程式與設備控制軟體的開發者。依作業系統送來的通知 → 恢復後會壞掉的東西 → 復原的設計 → 現場的調查這個順序,根據一次資訊整理。

1. 先講結論

設計的重心不是「一定要在睡眠前收拾乾淨」,而是「不論何時被停下來,恢復後都能重建」。要掌握的有三點。

  • 事前通知不等於保證你能把工作做完。睡眠無法拒絕,PBT_APMSUSPEND 的寬限大約只有 2 秒。緊急暫停時連通知本身都不會來。即使在 Modern Standby 上,也不能假設桌面應用程式在睡眠期間還會繼續執行。123
  • 設計時要假設連線、控制代碼與時間的連續性都無法跨過恢復而保留。除了恢復通知之外,也要讓通訊錯誤走進同一套重連處理,並重新檢視計時器的排程與經過時間的差值。
  • 抑制睡眠與為恢復做好準備,是兩件不同的對策。必要的工作區間用 SetThreadExecutionState 或電源要求,結束後務必清除。不過使用者明確的睡眠操作等情況擋不住,所以這不能當成省掉復原處理的理由。45

也可以依目的,直接從下列章節讀起。

想知道的事 該讀的章節
睡眠前後會收到哪些通知 第 2 章:電源事件的流程
Modern Standby 有什麼不同 第 3 章:系統的運作與應用程式的暫停
為什麼會出現連線錯誤或時刻偏差 第 4 章:經典的症狀
重連、時間管理與睡眠抑制該怎麼實作 第 5 章:耐得住恢復的設計
使用者回報該怎麼釐清原因 第 6 章:powercfg 與事件記錄

2. 睡眠前後會發生什麼 ── 電源事件的流程

作業系統會以 WM_POWERBROADCAST 訊息把電源狀態的變化通知應用程式。2 先掌握暫停轉換前後會用到的三個事件。與 Modern Standby 低耗電閒置的差異,在第 3 章補充。

事件 意義
PBT_APMSUSPEND 即將進入睡眠(最後的準備機會)
PBT_APMRESUMEAUTOMATIC 已恢復(恢復時一定會到)
PBT_APMRESUMESUSPEND 使用者操作造成的恢復(這個是有條件的)
睡眠與恢復的通知流程睡眠前一刻會收到約 2 秒寬限的 PBT_APMSUSPEND,恢復時一定會收到 PBT_APMRESUMEAUTOMATIC,只有使用者操作造成的恢復才會接著收到 PBT_APMRESUMESUSPEND應用程式OS應用程式OS睡眠(程式碼不會執行)PBT_APMSUSPEND(約 2 秒寬限)儲存狀態、關閉連線PBT_APMRESUMEAUTOMATIC(恢復時會到)重連、重建狀態PBT_APMRESUMESUSPEND(僅使用者恢復時)畫面更新等面向使用者的處理

圖 1: 通知只有「事前一句話,恢復後一句或兩句」。重建的主角是恢復側的處理。

睡眠前的通知是「來得及就做準備」的機會

PBT_APMSUSPEND 是進入睡眠前一刻的通知。可以關閉檔案、儲存狀態做準備,但有下列兩個限制。

限制 對設計的影響
每個應用程式的處理寬限大約 2 秒 超過時間,系統就不等應用程式而繼續往下走1
緊急暫停時沒有事前通知 電池電量危急這類情況,會在沒有準備的狀態下停止2

因此,「收到這個通知之後一定能存完」的設計並不成立。事前通知只在來得及的範圍內做準備,復原的主體放在恢復側。

一般睡眠與緊急暫停的差異一般睡眠會在事前送到約 2 秒準備時間的 PBT_APMSUSPEND,但電池電量危急這類緊急暫停沒有事前通知就停止,因此依賴事前通知的設計不成立一般的睡眠PBT_APMSUSPEND(約 2 秒寬限)準備之後停止緊急暫停(電池快沒電)沒有事前通知就停止假設通知一定會來的設計不成立

圖 2: 緊急暫停沒有預告。所以準備是「趕得上就賺到」,主體放在恢復側。

恢復通知要把機械性的復原與面向使用者的處理分開

從暫停恢復時,首先會收到 PBT_APMRESUMEAUTOMATIC。此外,若是以電源按鈕或按鍵恢復,或是恢復後偵測到使用者在場,接著還會收到 PBT_APMRESUMESUSPEND。67

另一方面,經由網路的遠端喚醒,或為了維護而進行的無人值守恢復,只會收到 PBT_APMRESUMEAUTOMATIC。重建連線這類必要的復原處理放在前者,畫面更新或重新登入要求這類面向使用者的動作放在後者。6

恢復兩階段的處理分工恢復時會到的 PBT_APMRESUMEAUTOMATIC 放重連這類機械性的重建,只有使用者操作才會到的 PBT_APMRESUMESUSPEND 放畫面更新或重新登入要求這類面向使用者的處理PBT_APMRESUMEAUTOMATIC(恢復時)機械性的重建PBT_APMRESUMESUSPEND(使用者恢復時)面向使用者的處理重連、重新開啟控制代碼畫面更新、重新登入要求

圖 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

傳統睡眠與 Modern Standby 的差異傳統 S3 睡眠讓整個系統停止,Modern Standby 下螢幕熄滅後系統仍會間歇運作。但桌面應用程式會被 DAM 暫停,因此兩者的應用程式程式碼都不會執行傳統的 S3 睡眠:整體停止應用程式的程式碼不會執行Modern Standby:系統間歇運作桌面應用程式被 DAM 暫停

圖 4: 模型變了,但對桌面應用程式來說結論一樣是「睡眠期間動不了」。

不要只把恢復通知當成復原的條件

Modern Standby 進出低耗電閒置的時機,有時並不與傳統的暫停轉換一致。也可能通知沒送到,連線卻已經壞了。請把恢復通知視為讓復原提早的輔助,把「從通訊錯誤的偵測就能重連」的路徑放在中心。具體的結構在第 5 章說明。

另外,由於進入低耗電狀態是分階段的,斷線與停止的時機不像 S3 那樣明確。使用者也難以分辨究竟是「只是螢幕熄了」還是「已經睡眠了」,所以詢問症狀時要確認有沒有闔上蓋子、閒置了幾分鐘。

4. 什麼會壞掉 ── 經典的症狀

恢復後的錯誤,分成連線、裝置控制代碼、時間的連續性三類來整理會比較清楚。共用資源還要確認重新驗證與重新建立網路所花的時間。

TCP 連線要等到傳送或接收才會發現斷了

睡眠期間,連線的對方以及 NAT、防火牆會把這一側的沉默當成逾時,把連線丟棄。但這一側的通訊端並不知情,所以要等到恢復後傳送或接收,才會第一次失敗。

也有一直停在等待接收、遲遲不會出錯的情況。這正是需要用 keepalive 確認連線死活的理由。資料庫連線與 WebSocket 也可以用同樣的架構來思考。

序列埠與 USB 裝置要把控制代碼重新開啟

USB 裝置在恢復時,有時看起來像是被拔掉又插回去一次,原本開著的控制代碼就開始傳回錯誤。這正是設備控制應用程式「只有午休之後才通訊錯誤」的典型模式。

不要以「控制代碼可以一直用下去」為前提,而要做成能重新開啟裝置的結構。重連的設計,在序列通訊的文章裡也有談到。

週期處理、經過時間與定時處理要分開重新檢視

與時間有關的問題分成下列三種。

處理 跨過睡眠會發生的事 對策
「每 10 秒輪詢一次」這類週期處理 睡眠期間會停止。恢復後何時觸發,依 API 與執行環境而異 恢復時重建排程
使用與上次時刻差值的計算 差值突然變成「8 小時份」,平均值或逾時判斷就會壞掉 對異常大的差值加上防護
「每晚 2 點執行」這類定時處理 那個時刻 PC 若在睡眠就不會執行 必要時使用工作排程器的喚醒功能

尤其是週期處理,過期的部分可能在恢復後立刻觸發一次,也可能到下一個週期為止什麼都不發生。重要的是不要把「沒能執行的那些次要怎麼處理」交給計時器的隱含行為。

時間連續性壞掉的三種形狀週期處理在睡眠期間會停止而且恢復後的觸發依 API 而異,所以恢復時要重建排程;與上次時刻的差值在恢復後會變得巨大,所以要加上防護;定時處理在機器睡著時不會執行,所以要考慮工作排程器的喚醒功能週期處理:睡眠期間停止恢復時重建排程經過時間的差值:變得巨大對異常的差值加上防護定時處理:睡著而未執行用喚醒設定把機器叫醒

圖 5: 計時器與時刻的處理要以「時間會跳」為前提來寫。三種形狀各有對應的對策類型。

跨過睡眠就會壞掉的三件事跨過睡眠後,TCP 連線會被對方逾時丟棄,USB 裝置的控制代碼被當成重新連接而失效,依經過時間的處理會觀測到巨大的時間跳躍。分別用重連、重新開啟與差值防護來重建睡眠區間TCP 連線:對方已丟棄USB 裝置:控制代碼失效經過時間:巨大的跳躍偵測錯誤並重連把裝置重新開啟對異常的差值加上防護

圖 6: 會壞的東西分成「連線」「控制代碼」「時間連續性」三個系統,各自都有既定的重建類型。

網路磁碟機與 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);
}

重連要把「冪等、退避、keepalive」組合起來

重連處理要備齊下列三點。

要素 作用
冪等的重連 讓它被要求幾次都能安全重建
帶指數退避的重試 失敗就拉長到下次重試的間隔
穩態時的 keepalive 確認連線是否還活著,及早偵測斷線

這三件一套不只對睡眠恢復有用,對網路瞬斷或裝置重新啟動也一樣管用。

耐得住恢復的重連設計恢復通知、通訊錯誤與 keepalive 失敗都匯入同一套冪等的重連處理,失敗時以指數退避重試是否恢復通知(PBT_APMRESUMEAUTOMATIC)冪等的重連處理偵測到通訊錯誤keepalive 失敗成功?回到正常運作指數退避後重試

圖 7: 把重連集中成一條冪等的處理,從恢復通知、錯誤偵測或 keepalive 都走進同一條路。

不要把時間跳過的區間直接混進計算

在使用「距離上次經過多久」的處理裡,偵測到異常大的差值就加上讓該區間失效的防護。目的是不要把它混進平均,也不要當成一般的逾時來處理。

量測跨過恢復的時間時,要區分睡眠期間仍會前進的時刻(實際時間)與實際花在處理上的時間。週期處理的排程重建,以及定時處理沒有執行時的處理方式,也請依第 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 睡不著的原因 處理結束後務必清除
抑制睡眠的兩種手段無論用方便的 SetThreadExecutionState,還是能附上原因字串、管理員可用 powercfg 看到的電源要求 API,處理結束時都務必清除不想被睡眠打斷的處理區間SetThreadExecutionState電源要求(PowerSetRequest)方便、只要指定旗標附上原因、powercfg 看得到處理結束時務必清除

圖 8: 兩種手段都把「做完就清除」當成絕對條件。能讓原因可見的電源要求對維運比較貼心。

若持續運作是需求,就重新檢視放在哪裡與怎麼維運

服務或無視窗的應用程式,一樣能用 RegisterSuspendResumeNotification 的 DEVICE_NOTIFY_CALLBACK 收到通知。8

不過,如果真的需要持續運作,就要重新檢視「常駐在會睡眠的用戶端 PC」這件事本身。把處理搬到伺服器側,或搬到不睡眠維運的電腦上,才是根本的對策。

6. 怎麼調查 ── powercfg 與事件記錄

收到使用者回報時,先確認「錯誤發生前一刻 PC 是不是在睡眠」。問清楚有沒有闔上蓋子、閒置了幾分鐘之後,再用符合症狀的工具。

想查的事 工具 要確認的內容
睡不著的原因 powercfg /requests 正在發出電源要求的處理程序與驅動程式。也一併確認 SetThreadExecutionState 是否忘了清除
自己醒來的原因 powercfg /lastwake、powercfg /waketimers 最近一次的喚醒原因,以及目前預約喚醒的計時器
Modern Standby 的品質 powercfg /sleepstudy 每個睡眠區間的耗電與活動9
睡眠與恢復的時間軸 系統事件記錄的 Kernel-Power 進入睡眠與恢復的紀錄

把事件記錄和應用程式的記錄對起來,就能客觀確認「錯誤發生前一刻有沒有恢復」。不要停在懷疑睡眠,而要對齊發生時刻來釐清原因。

電源問題的症狀與調查指令的對應睡不著的症狀用 powercfg /requests 查是誰發出電源要求,自己醒來的症狀用 /lastwake 與 /waketimers 查喚醒原因,確認時間軸則用事件記錄裡的 Kernel-Power睡不著powercfg /requests自己醒來powercfg /lastwake、/waketimers想確認時間軸事件記錄裡的 Kernel-Power忘了清除的睡眠抑制也會現形

圖 9: 症狀與調查指令的對應分成三個系統。先確認「前一刻有沒有睡眠」再分開使用。

7. 總結

睡眠的對策,光是收到通知並不算完成。要把事前準備來不及、恢復通知沒送到這兩件事都算進去,像下面這樣分工。

設計與調查的對象 要掌握的重點
睡眠前 無法拒絕。用 PBT_APMSUSPEND 約 2 秒的寬限做準備,但緊急時通知不會來
恢復通知 用 PBT_APMRESUMEAUTOMATIC 做必要的復原,用 PBT_APMRESUMESUSPEND 做面向使用者的處理。不要只依賴通知
連線與控制代碼 以「由通訊錯誤觸發的重連」為中心,組合冪等性、指數退避與 keepalive
時間 對異常的經過時間差值加上防護,並重建週期處理的排程。定時處理在睡著時不會執行
睡眠抑制 只在必要的區間設定,結束後清除。對明確的睡眠操作等情況,仍要保留復原的準備
原因調查 詢問前一刻是否睡過,並把 powercfg 與 Kernel-Power 的紀錄和應用程式的記錄比對

從應用程式的角度看,睡眠是「時間毫無預告地跳躍、與周邊的連線被切斷,然後又回來」的事件。能不能把它當成日常的一部分而非異常狀況織進設計,決定了筆電時代業務應用程式的穩定度。

相關文章

相關諮詢領域

小村軟體有限公司承接「從睡眠恢復後通訊就壞掉」「與裝置的連線在午休之後就斷」這類問題的原因調查、為既有應用程式補上重連邏輯與電源事件處理,以及以筆電維運為前提的業務應用程式與設備控制軟體的設計審查。

參考連結

  1. Microsoft Learn, PBT_APMSUSPEND event. 關於這是電腦即將進入暫停狀態前一刻送到的事件;關於應用程式應完成儲存資料所需的處理;以及系統允許處理這個通知的時間大約是 2 秒,超過還繼續處理的應用程式可能被中斷。 ↩ ↩2

  2. Microsoft Learn, System Power Management Events. 關於系統會事先廣播睡眠等運作模式的變更;關於閒置睡眠前會通知 PBT_APMSUSPEND,讓應用程式能做關閉檔案、儲存資料的準備;關於緊急暫停(電池電量危急等)不會進行事前通知;關於處理這個訊息時每個應用程式最多約 2 秒,逾時後就會被切斷;以及恢復時所有應用程式都會收到通知。 ↩ ↩2 ↩3

  3. Microsoft Learn, Prepare software for modern standby. 關於轉換到 Modern Standby 的第一個階段,Desktop Activity Moderator(DAM)會暫停桌面應用程式;以及系統其後會分階段進入低耗電階段與韌性階段,只有獲准的元件才會間歇運作。 ↩ ↩2 ↩3

  4. Microsoft Learn, SetThreadExecutionState function (winbase.h). 關於用 ES_SYSTEM_REQUIRED 或 ES_DISPLAY_REQUIRED 可以抑制系統的閒置睡眠與螢幕關閉;以及用 ES_CONTINUOUS 宣告持續抑制,用完之後單獨呼叫 ES_CONTINUOUS 來清除的用法。 ↩ ↩2

  5. Microsoft Learn, PowerSetRequest function (winbase.h). 關於可以對用 PowerCreateRequest 建立的電源要求物件設定維持系統、維持螢幕等要求類型;關於可以附上診斷用的原因字串;以及尚未解除的電源要求可以用 powercfg /requests 列舉。 ↩ ↩2 ↩3

  6. Microsoft Learn, PBT_APMRESUMESUSPEND event. 關於使用者操作造成的恢復,或其後偵測到使用者輸入時,會接在 PBT_APMRESUMEAUTOMATIC 之後送出;關於遠端喚醒這類外部原因造成的恢復只會送出 PBT_APMRESUMEAUTOMATIC;以及應用程式應重新開啟睡眠時關閉的檔案並準備接受使用者輸入。 ↩ ↩2

  7. Microsoft Learn, WM_POWERBROADCAST message. 關於恢復時一定會送出 PBT_APMRESUMEAUTOMATIC,使用者輸入造成的恢復還會再送出 PBT_APMRESUMESUSPEND;關於這個訊息無法區分低耗電狀態的種類;關於電源狀態轉換的細節會記錄在系統事件記錄;以及要防止系統進入低耗電狀態就呼叫 SetThreadExecutionState。 ↩ ↩2 ↩3

  8. Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). 關於這是註冊接收暫停與恢復通知的 API,除了把訊息送到視窗控制代碼之外,指定 DEVICE_NOTIFY_CALLBACK 還能讓沒有視窗的應用程式或服務以回呼收到通知。 ↩ ↩2

  9. Microsoft Learn, Modern standby SleepStudy. 關於用 powercfg /sleepstudy 產生的報告,可以確認每個 Modern Standby 區間的耗電、活動與喚醒原因(電源按鈕、使用者輸入、喚醒計時器等)。 ↩

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

應用程式能事先得知睡眠並拒絕嗎?
在現行 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 轉序列埠這類裝置,恢復時有時會被當成裝置拔除再插入,原本開著的控制代碼就失效。兩者都應假設「控制代碼和連線跨過恢復活不下來」,正解是在恢復通知或通訊錯誤時重建連線的重連邏輯。週期性 keepalive 加上失敗時指數退避重試,是既定做法。
要怎麼調查非預期的睡眠或非預期的恢復?
第一個工具是 powercfg 指令。「睡不著」方向用 powercfg /requests 列出哪些處理程序和驅動程式發出了擋住睡眠的電源要求。「自己醒來」方向用 powercfg /lastwake 看最近一次喚醒原因,用 powercfg /waketimers 看目前預約要喚醒機器的計時器。Modern Standby 機器上,powercfg /sleepstudy 會產出睡眠期間耗電與活動的報告。睡眠與恢復的歷史也會記在事件記錄(系統記錄的 Kernel-Power 來源),因此能依時間軸確認「何時睡了、何時因何醒來」。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽