Ứng dụng hỏng khi thức dậy từ ngủ — Sự kiện nguồn Windows và cách viết ứng dụng nghiệp vụ sống sót
· Go Komura · Windows, Quản lý nguồn, Phát triển Windows, Ứng dụng nghiệp vụ, Điều khiển thiết bị, Xử lý sự cố, Win32 API
“Tôi đóng laptop, mở ra sáng hôm sau, và ứng dụng nghiệp vụ đầy lỗi.” “Ứng dụng giám sát thiết bị chỉ mất dữ liệu sau giờ ăn trưa.” “Công cụ thường trú xuất sang Excel đôi khi dừng vì lỗi kết nối.” — Những phiếu này chia một nghi phạm duy nhất. Ngủ.
Ứng dụng nghiệp vụ từ thời máy để bàn là chủ đạo được viết trên giả định ngầm rằng “PC cứ bật”. Chiến trường chính hôm nay là laptop, và mặc định nó ngủ sau vài phút rảnh. Trên máy hỗ trợ Modern Standby, chính ngữ nghĩa của ngủ đã đổi so với mô hình truyền thống. Hướng tới các nhà phát triển viết ứng dụng nghiệp vụ và phần mềm điều khiển thiết bị trên Windows, bài viết này sắp xếp, từ nguồn gốc, những gì hệ điều hành thông báo cho ứng dụng trước và sau khi ngủ, cái gì vỡ, và cách viết ứng dụng sống sót khi thức dậy.
1. Kết luận trước
- Ngủ là sự kiện ứng dụng “không có quyền từ chối”. Bạn được thông báo ngay trước đó bằng
WM_POWERBROADCAST(PBT_APMSUSPEND), nhưng thời gian ân hạn khoảng 2 giây, và trên lần tạm dừng khẩn cấp thì thông báo còn không tới.12 - Khi thức dậy từ tạm dừng, PBT_APMRESUMEAUTOMATIC tới, và trên lần thức dậy do người dùng thì PBT_APMRESUMESUSPEND cũng tới. Việc bắt buộc như kết nối lại thuộc về cái trước theo quy tắc. Chuyển vào và ra khỏi rảnh nguồn thấp của Modern Standby không luôn khớp các thông báo này, nên hãy coi thông báo như trợ giúp.34
- Thiết kế trên giả định rằng kết nối TCP, cổng serial và handle thiết bị không sống sót qua lần thức dậy. Logic kết nối lại xây lại chúng khi có thông báo thức dậy hoặc lỗi giao tiếp là sự kiện chính.
- Để ý bộ hẹn giờ và cách xử lý thời gian. Việc định kỳ dừng trong lúc ngủ, và cách nó kích ngay sau khi thức dậy khác nhau theo API bộ hẹn giờ và runtime. “Bước nhảy khổng lồ của thời gian đã trôi” cũng xảy ra, nên cách an toàn là xây lại lịch khi thức dậy.
- Ức chế ngủ tường minh cho các khoảng bạn không muốn bị ngủ xuyên qua. Dùng
SetThreadExecutionState(ES_SYSTEM_REQUIRED) hoặc yêu cầu nguồn (PowerSetRequest), và luôn gỡ khi việc xong.45 - Trên máy Modern Standby hệ thống vẫn chạy gián đoạn trong lúc ngủ, nhưng ứng dụng desktop bị tạm dừng. Bạn không thể giữ kỳ vọng rằng “ứng dụng của chúng ta nên tiếp tục chạy trong lúc ngủ”.6
- Công cụ điều tra chuẩn là
powercfg(/requests, /lastwake, /sleepstudy) và Kernel-Power trong nhật ký sự kiện.
2. Điều gì xảy ra quanh ngủ — Luồng sự kiện nguồn
Hệ điều hành phát thay đổi trạng thái nguồn tới mọi ứng dụng như thông điệp WM_POWERBROADCAST.2 Có ba sự kiện chính liên quan ngủ và thức dậy.
| Sự kiện | Ý nghĩa |
|---|---|
| PBT_APMSUSPEND | Sắp vào ngủ (cơ hội cuối để chuẩn bị) |
| PBT_APMRESUMEAUTOMATIC | Đã thức dậy (luôn tới khi thức dậy) |
| PBT_APMRESUMESUSPEND | Thức dậy do hành động của người dùng (cái này có điều kiện) |
PBT_APMSUSPEND là thông báo ngay trước khi ngủ, và ở đây bạn có thể chuẩn bị bằng cách đóng tệp và lưu trạng thái. Có hai điều kiện, tuy nhiên. Thứ nhất, thời gian được phép xử lý khoảng 2 giây mỗi ứng dụng, và nếu vượt quá thì hệ thống tiếp tục mà không chờ.1 Thứ hai, trên lần tạm dừng khẩn cấp như pin cực thấp, nó ngủ ngay mà không có thông báo trước.2 Thiết kế “phải xong trước khi ngủ” không đứng vững. Coi thông báo như cơ hội “làm nếu kịp”, và đặt việc chính về phía thức dậy.
Phía thức dậy là hai giai đoạn. PBT_APMRESUMEAUTOMATIC tới khi thức dậy từ một chuyển tiếp tạm dừng. Bên trên đó, nếu máy thức dậy vì hành động của người dùng như nút nguồn hoặc nhấn phím (hoặc sự hiện diện của người dùng được phát hiện sau đó), PBT_APMRESUMESUSPEND theo sau. Ngược lại, lần thức dậy không có người dùng cho đánh thức từ xa qua mạng hoặc cho bảo trì chỉ gửi PBT_APMRESUMEAUTOMATIC.3 Hai giai đoạn này tự chúng là gợi ý để tách việc — làm phục hồi cơ học như xây lại kết nối trên PBT_APMRESUMEAUTOMATIC, và làm hành động hướng tới người dùng như cập nhật màn hình hoặc nhắc đăng nhập lại trên PBT_APMRESUMESUSPEND.
sequenceDiagram
accTitle: Luồng thông báo cho ngủ và thức dậy
accDescr: PBT_APMSUSPEND tới ngay trước khi ngủ với khoảng 2 giây ân hạn; khi thức dậy, PBT_APMRESUMEAUTOMATIC luôn tới, và PBT_APMRESUMESUSPEND chỉ theo sau cho lần thức dậy do người dùng
participant OS as OS
participant A as Ứng dụng
OS->>A: PBT_APMSUSPEND (khoảng 2 giây ân hạn)
A->>A: Lưu trạng thái và đóng kết nối
Note over OS: Ngủ (mã không chạy)
OS->>A: PBT_APMRESUMEAUTOMATIC (tới khi thức dậy)
A->>A: Kết nối lại và khôi phục trạng thái
OS->>A: PBT_APMRESUMESUSPEND (chỉ thức dậy do người dùng)
A->>A: Cập nhật màn hình và việc hướng tới người dùng khác
Hình 1: Thông báo chỉ là “một lời ngay trước, và một hoặc hai lời sau khi thức dậy”. Ngôi sao của phục hồi là việc ở phía thức dậy.
flowchart TB
accTitle: Khác biệt giữa ngủ thông thường và tạm dừng khẩn cấp
accDescr: Ngủ thông thường gửi PBT_APMSUSPEND ngay trước với khoảng 2 giây để chuẩn bị, nhưng tạm dừng khẩn cấp như pin cực thấp dừng mà không có thông báo trước, nên thiết kế phụ thuộc thông báo trước không đứng vững
n2["Ngủ thông thường"] --> pre["PBT_APMSUSPEND(khoảng 2 giây ân hạn)"]
pre --> s1["Chuẩn bị, rồi dừng"]
e2["Tạm dừng khẩn cấp(pin cực thấp)"] --> s2["Dừng không có thông báo trước"]
s2 -.-> l2["Thiết kế giả định thông báo sẽ tới không đứng vững"]
Hình 2: Tạm dừng khẩn cấp tới không báo trước. Nên chuẩn bị là “thưởng nếu kịp”, và việc chính nằm ở phía thức dậy.
Lưu ý rằng WM_POWERBROADCAST không phân biệt loại trạng thái nguồn thấp (ngủ so với ngủ đông).4 Sự trừu tượng đúng cho ứng dụng là coi nó như một loại sự kiện: “nó dừng, rồi nó trở lại”. Dịch vụ không cửa sổ và ứng dụng console có thể nhận cùng thông báo bằng RegisterSuspendResumeNotification ở dạng callback (DEVICE_NOTIFY_CALLBACK).7
flowchart TB
accTitle: Tách việc qua hai giai đoạn thức dậy
accDescr: Đặt phục hồi cơ học như kết nối lại trên PBT_APMRESUMEAUTOMATIC, thứ tới khi thức dậy; đặt việc hướng tới người dùng như cập nhật màn hình hoặc nhắc đăng nhập lại trên PBT_APMRESUMESUSPEND, thứ chỉ tới trên lần thức dậy do người dùng
ra["PBT_APMRESUMEAUTOMATIC(khi thức dậy)"] --> m["Phục hồi cơ học"]
rs["PBT_APMRESUMESUSPEND(thức dậy do người dùng)"] --> u["Việc hướng tới người dùng"]
m -.-> m1["Kết nối lại và mở lại handle"]
u -.-> u1["Cập nhật màn hình và nhắc đăng nhập lại"]
Hình 3: Cái sau không tới trên lần thức dậy không có người dùng, nên đặt phục hồi bắt buộc vào cái sau sẽ bỏ lỡ nó.
3. Modern Standby — Ý nghĩa của “ngủ” đã đổi
Một thực tế hiện đại nữa cần tiếp nhận là Modern Standby. Ngủ S3 truyền thống là mô hình đơn giản “dừng hệ thống như một khối”; ngủ trên máy Modern Standby là mô hình giống điện thoại, trong đó hệ thống vẫn chạy gián đoạn sau khi màn hình tắt.
Điều quan trọng với ứng dụng nghiệp vụ ở đây là ứng dụng desktop bị tạm dừng bởi Desktop Activity Moderator (DAM) ở giai đoạn đầu tiên của việc vào ngủ.6 Bản thân hệ thống vẫn chạy lúc này lúc khác để giữ mạng và nhận thông báo, nhưng các thành phần hưởng lợi từ đó là những thành phần tham gia cơ chế này — mã ứng dụng desktop thông thường không chạy. Vì vậy từ góc độ nhà phát triển, kết luận giống nhau cho Modern Standby và cho S3 — thiết kế trên giả định rằng mã của bạn không chạy trong lúc ngủ.
flowchart TB
accTitle: Khác biệt giữa ngủ truyền thống và Modern Standby
accDescr: Ngủ S3 truyền thống dừng hệ thống như một khối, trong khi dưới Modern Standby hệ thống vẫn chạy gián đoạn sau khi màn hình tắt. Ứng dụng desktop bị DAM tạm dừng trong cả hai trường hợp, nên mã của ứng dụng không chạy
s3["Ngủ S3 truyền thống: cả hệ thống dừng"] --> conc["Mã của ứng dụng không chạy"]
ms["Modern Standby: hệ thống chạy gián đoạn"] --> dam["Ứng dụng desktop bị DAM tạm dừng"]
dam --> conc
Hình 4: Mô hình đã đổi, nhưng với ứng dụng desktop kết luận giống nhau: “bạn không thể chạy trong lúc ngủ”.
Một lưu ý nữa là bạn có thể dựa vào thông báo ít đến mức nào. Dưới Modern Standby, chuyển vào và ra khỏi rảnh nguồn thấp không khớp chuyển tiếp tạm dừng truyền thống, và một kết nối có thể đã đứt mà thông báo không bao giờ tới. Coi thông báo thức dậy như trợ giúp, và đặt kết nối lại kích hoạt bởi phát hiện lỗi (Chương 5) trên đường phục hồi chính.
Một khác biệt nữa là cảm giác “trơn” của hành vi. Đạt tới độ sâu của ngủ là theo giai đoạn, và thời điểm ngắt kết nối cùng dừng không sắc như dưới S3. Sự phân biệt giữa “màn hình vừa tắt” và “nó đã ngủ” cũng khó để người dùng thấy, nên khi nhận một triệu chứng bạn cần xác nhận “họ có đóng nắp không” và “nó bị để rảnh bao nhiêu phút”.
4. Cái gì vỡ — Các triệu chứng kinh điển
Kết nối TCP đã chết. Trong lúc ngủ, bên kia, NAT và tường lửa coi sự im lặng của bạn là hết thời gian chờ và hủy kết nối. Tệ hơn, socket phía này không biết về lỗi, nên nó chỉ thất bại khi bạn gửi hoặc nhận sau khi thức dậy. Hoặc tệ hơn nữa, lần chờ nhận không bao giờ lỗi cả (đó là lý do bạn cần keepalive). Kết nối cơ sở dữ liệu và WebSocket có cùng hình dạng.
Handle cổng serial và thiết bị USB trở nên không hợp lệ. Một thiết bị kết nối USB có thể trông, khi thức dậy, như thể nó bị “rút ra rồi cắm lại” một lần, và handle bạn đã mở bắt đầu trả về lỗi. Đó là mẫu điển hình của ứng dụng điều khiển thiết bị “chỉ gặp lỗi giao tiếp sau giờ ăn trưa”. Thiết kế kết nối lại cũng được trình bày trong bài viết về giao tiếp serial.
Tính liên tục của thời gian bị đứt. Việc do bộ hẹn giờ dẫn như “thăm dò mỗi 10 giây” không kích trong lúc ngủ. Cách nó kích ngay sau khi thức dậy (việc đến hạn đã hết hạn kích một lần ngay, không có gì xảy ra cho tới chu kỳ tiếp, và tương tự) khác nhau theo API bộ hẹn giờ và runtime bạn đang dùng, nên đừng để việc xử lý các nhịp bị bỏ lỡ cho hành vi ngầm — cách an toàn là xây lại lịch trên thông báo thức dậy. Ngoài ra, tính toán thời gian đã trôi (hiệu với dấu thời gian trước) đột nhiên trở thành “trị giá 8 giờ”, và tính trung bình hoặc phán đoán hết thời gian chờ bị vỡ. Việc theo lịch như “chạy mỗi đêm lúc 2 giờ sáng” đơn giản không chạy nếu PC đang ngủ lúc đó (đánh thức nó bằng tính năng thức-từ-ngủ của Task Scheduler nếu bạn cần).
flowchart TB
accTitle: Ba hình dạng trong đó tính liên tục của thời gian bị đứt
accDescr: Việc định kỳ dừng trong lúc ngủ và lần kích sau thức dậy khác theo API, nên xây lại lịch khi thức dậy; hiệu với dấu thời gian trước trở nên khổng lồ sau thức dậy, nên bảo vệ nó; việc theo lịch không chạy nếu máy đang ngủ, nên cân nhắc thức-từ-ngủ của Task Scheduler
t1["Việc định kỳ: dừng"] -.-> g1["Xây lại lịch"]
t2["Thời gian đã trôi: nổ"] -.-> g2["Bảo vệ hiệu bất thường"]
t3["Theo lịch: không bao giờ chạy"] -.-> g3["Thức-từ-ngủ"]
g1 ~~~ t2
g2 ~~~ t3
Hình 5: Viết xử lý bộ hẹn giờ và thời gian trên giả định rằng “thời gian nhảy”. Mỗi trong ba hình dạng có một loại đối sách.
flowchart TB
accTitle: Ba thứ vỡ xuyên qua ngủ
accDescr: Xuyên qua ngủ, kết nối TCP đã bị bên kia hủy vì hết thời gian chờ, handle thiết bị USB bị vô hiệu như một lần kết nối lại, và việc dựa trên thời gian đã trôi quan sát một bước nhảy thời gian khổng lồ. Phục hồi mỗi thứ bằng kết nối lại, mở lại, và bảo vệ hiệu
sleep["Khoảng ngủ"] --> tcp["TCP: đối phương đã hủy"]
sleep --> more{"USB hay thời gian đã trôi?"}
more --> usb["USB: handle không hợp lệ"]
more --> time["Thời gian đã trôi: một bước nhảy"]
tcp -.-> r1["Phát hiện + kết nối lại"]
usb -.-> r2["Mở lại thiết bị"]
time -.-> r3["Bảo vệ hiệu bất thường"]
Hình 6: Cái gì vỡ rơi vào ba họ — “kết nối”, “handle”, và “tính liên tục của thời gian” — và mỗi họ có một loại phục hồi đã ổn định.
Xác thực lại với tài nguyên dùng chung. Ổ mạng và VPN thường cần được thiết lập lại sau khi thức dậy, và có một “thung lũng khởi động” vài tới vài chục giây ngay sau khi thức dậy trong đó truy cập thất bại. An toàn hơn là không thử lại mọi thứ cùng lúc ngay sau khi thức dậy, mà chờ một chút rồi thử lại theo giai đoạn.
5. Xây ứng dụng sống sót khi thức dậy
Nguyên tắc là một điều. Giả định rằng “kết nối và handle không sống sót xuyên qua ngủ”, và cấu trúc ứng dụng sao cho bạn luôn có thể phục hồi.
Phát hiện thức dậy và phục hồi. Khi WM_POWERBROADCAST của cửa sổ cấp cao nhất nhận PBT_APMRESUMEAUTOMATIC, hủy các kết nối bạn đang giữ và xây lại chúng. Điểm then chốt là không dựa một mình vào thông báo thức dậy. Thông báo bị bỏ lỡ và giao tiếp xảy ra trước thông báo đều có thật, nên luôn ghép nó với một đường “kết nối lại khi phát hiện lỗi giao tiếp”, và coi thông báo thức dậy như một kích hoạt chỉ khởi động việc đó sớm hơn.
// C#: funnel both the resume notification and communication errors into the same reconnect path
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(); // idempotent reconnect request
}
base.WndProc(ref m);
}
Làm bản thân việc kết nối lại idempotent (an toàn dù được gọi bao nhiêu lần), thử lại khi thất bại với lùi mũ, và ở trạng thái ổn định phát hiện sớm kết nối chết bằng keepalive — ghép ba thứ đó thành một bộ, và bạn sẽ sống sót không chỉ thức dậy từ ngủ mà cả lần mạng rớt ngắn hoặc thiết bị khởi động lại.
flowchart TB
accTitle: Thiết kế kết nối lại bền với thức dậy
accDescr: Thông báo thức dậy, lỗi giao tiếp, và thất bại keepalive đều đổ vào cùng việc kết nối lại idempotent, thứ thử lại với lùi mũ khi thất bại
e1["Thông báo thức dậy(PBT_APMRESUMEAUTOMATIC)"] --> r["Việc kết nối lại idempotent"]
e2["Phát hiện lỗi giao tiếp"] --> r
e3["Thất bại keepalive"] --> r
r --> ok{"Thành công?"}
ok -->|"có"| run["Trở lại vận hành bình thường"]
ok -->|"không"| back["Thử lại sau lùi mũ"]
back --> r
Hình 7: Tập trung kết nối lại vào một đường idempotent duy nhất, và vào cùng con đường đó từ thông báo thức dậy, phát hiện lỗi, hoặc keepalive.
Xem lại cách xử lý thời gian. Với việc dùng “thời gian đã trôi kể từ lần trước”, đặt một bảo vệ vô hiệu hóa khoảng khi nó phát hiện hiệu lớn bất thường (đừng gấp nó vào trung bình, đừng coi nó như hết thời gian chờ). Đo thời gian đã trôi xuyên qua thức dậy đòi hỏi giữ sự phân biệt giữa đồng hồ tiến trong lúc ngủ (thời gian tường) và thời gian thực sự dành cho việc.
Ức chế ngủ tường minh cho các khoảng bạn không muốn bị ngủ xuyên qua. Trong lúc việc không được ngủ xuyên qua — di chuyển dữ liệu, giao tiếp liên tục với thiết bị, và tương tự — bạn có thể giữ hệ thống thức bằng SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (thêm ES_DISPLAY_REQUIRED nếu bạn cũng muốn giữ màn hình bật).45 Phương pháp cư xử tốt hơn là API yêu cầu nguồn (PowerCreateRequest + PowerSetRequest), thứ có thể gắn một chuỗi lý do, và powercfg /requests sẽ hiện “ai đang chặn và vì sao”.8 Lưu ý rằng ức chế qua SetThreadExecutionState là theo từng luồng, và bạn gỡ nó từ cùng luồng đã đặt. Với việc đổi luồng, như async/await, dùng phía yêu cầu nguồn, thứ được quản lý bằng handle. Có những lưu ý. Thứ nhất, những gì chúng ức chế là ngủ rảnh tự động. Chúng không chặn được hành động tường minh của người dùng như đóng nắp hoặc chọn Sleep từ menu Start, nên bạn không thể bỏ thiết kế kết nối lại của chương này ngay cả khi đang ức chế. Thứ hai, khi chạy pin trên máy Modern Standby, các yêu cầu nguồn này cũng bị cắt một thời gian sau khi hết thời gian chờ ngủ. Việc không thể bị gián đoạn phải được bảo đảm bằng nguồn AC hoặc bằng vận hành.8 Thứ ba, luôn gỡ khi việc xong. Một lần quên gỡ trở thành lỗi mới: “PC này, vì lý do nào đó, không chịu ngủ”.
flowchart TB
accTitle: Hai phương tiện ức chế ngủ
accDescr: Dù bạn dùng SetThreadExecutionState tiện lợi hay API yêu cầu nguồn có thể gắn chuỗi lý do và hiện cho quản trị viên qua powercfg, luôn gỡ khi việc kết thúc
need["Khoảng việc không được ngủ xuyên qua"] --> a["SetThreadExecutionState"]
need --> b["Yêu cầu nguồn(PowerSetRequest)"]
a -.-> a1["Tiện — chỉ cờ"]
b -.-> b1["Có lý do — thấy trong powercfg"]
a --> off["Luôn gỡ khi việc kết thúc"]
b --> off
Hình 8: Với cả hai phương tiện, “gỡ khi xong” là điều kiện tuyệt đối. Yêu cầu nguồn có thể làm lý do hiện ra thì thân thiện hơn với vận hành.
Dịch vụ và ứng dụng không cửa sổ nhận thông báo callback bằng RegisterSuspendResumeNotification (DEVICE_NOTIFY_CALLBACK).7 Nếu vận hành liên tục là yêu cầu thật, giải pháp gốc là xem lại thiết kế giữ việc thường trú trên PC client sẽ ngủ, và chuyển nó sang phía máy chủ hoặc sang máy được vận hành không ngủ.
6. Điều tra — powercfg và nhật ký sự kiện
Điều tra quanh nguồn được phục vụ tốt bởi các công cụ đi cùng hệ điều hành.
- Nó không chịu ngủ:
powercfg /requestsliệt kê tiến trình và driver đã phát yêu cầu nguồn. “Ứng dụng quên gỡSetThreadExecutionState” cũng hiện ở đây. - Nó tự thức:
powercfg /lastwakecho lý do thức gần nhất, vàpowercfg /waketimerscho các bộ hẹn giờ hiện đang được giữ chỗ để đánh thức máy. - Chất lượng Modern Standby:
powercfg /sleepstudytạo báo cáo tiêu thụ nguồn và hoạt động theo từng khoảng ngủ.9 - Xác nhận dòng thời gian: Nguồn Kernel-Power trong nhật ký sự kiện (System) giữ bản ghi vào ngủ và thức dậy. Khớp chúng với nhật ký của ứng dụng cho phép bạn xác nhận khách quan liệu “có một lần thức dậy ngay trước lỗi”.
flowchart TB
accTitle: Ánh xạ triệu chứng sự cố nguồn sang lệnh điều tra
accDescr: Với triệu chứng không-chịu-ngủ, tìm ai giữ yêu cầu nguồn bằng powercfg /requests; với triệu chứng tự-thức, tìm lý do thức bằng /lastwake và /waketimers; với dòng thời gian, dùng Kernel-Power trong nhật ký sự kiện
s1["Nó không chịu ngủ"] --> c1["powercfg /requests"]
s2["Nó tự thức"] --> c2["powercfg /lastwake và /waketimers"]
s3["Muốn xác nhận dòng thời gian"] --> c3["Kernel-Power trong nhật ký sự kiện"]
c1 -.-> note["Ức chế ngủ bị quên cũng hiện"]
Hình 9: Triệu chứng ánh xạ sang lệnh điều tra theo ba họ. Trước hết xác nhận “nó vừa ngủ chưa”, rồi tách.
Trong xử lý phiếu, chỉ cần hỏi trước “PC có đang ngủ ngay trước đó không (họ có đóng nắp không)” đã tăng tốc cô lập rất nhiều.
7. Tóm tắt
- Ngủ không thể từ chối. Thông báo trước (PBT_APMSUSPEND) là nỗ lực tốt nhất với khoảng 2 giây ân hạn, và nó không tới trong trường hợp khẩn cấp. Đặt thiết kế chính về phía thức dậy.
- Thông báo thức dậy là PBT_APMRESUMEAUTOMATIC (khi thức dậy từ tạm dừng) + PBT_APMRESUMESUSPEND (khi hành động của người dùng). Giữ kết nối lại do lỗi dẫn trên đường chính cho trường hợp thông báo không tới.
- Giả định rằng kết nối và handle không sống sót qua lần thức dậy, và triển khai bộ ba gồm kết nối lại idempotent + lùi mũ + keepalive.
- Đặt bảo vệ chống “hiệu bất thường” trên việc dựa trên thời gian đã trôi. Thiết kế việc theo lịch trên giả định rằng nó không chạy trong lúc ngủ.
- Với các khoảng không được ngủ xuyên qua, ức chế ngủ tường minh bằng
SetThreadExecutionStatehoặc yêu cầu nguồn, và luôn gỡ khi xong. - Điều tra là
powercfg(/requests, /lastwake, /sleepstudy) và nhật ký sự kiện Kernel-Power. Trong xử lý phiếu, hỏi trước “nó có ngủ ngay trước đó không”.
Từ góc độ ứng dụng, ngủ là sự kiện trong đó “thời gian nhảy không báo trước, kết nối với xung quanh bị cắt, rồi nó trở lại”. Việc bạn đã đan điều đó vào thiết kế như một phần của đời thường, chứ không như tình huống bất thường, là thứ tách sự ổn định của ứng dụng nghiệp vụ ở thời đại laptop.
Bài viết liên quan
- Những cái bẫy của ứng dụng giao tiếp serial — Qua thiết kế kết nối lại và nhật ký
- Tắt Windows nhìn từ ứng dụng của bạn — Sống sót đúng với thông báo thoát, khởi động lại và mất điện
- Chế độ Hiệu quả của Windows là gì? — Biểu tượng lá xanh và cách tắt
- Vì sao nên ưu tiên chờ sự kiện hơn Sleep(1) trên Windows
- “Không phản hồi” thực sự là gì — Cách Windows quyết định ứng dụng đã treo, và cách thiết kế ứng dụng không treo
Lĩnh vực tư vấn liên quan
KomuraSoft LLC đảm nhận điều tra nguyên nhân gốc rễ của lỗi như “giao tiếp đứt sau khi thức dậy từ ngủ” và “kết nối tới thiết bị rớt sau giờ ăn trưa”, gắn thêm logic kết nối lại và xử lý sự kiện nguồn vào ứng dụng hiện có, và rà soát thiết kế ứng dụng nghiệp vụ cùng phần mềm điều khiển thiết bị giả định vận hành trên laptop.
- Điều tra lỗi và phân tích nguyên nhân gốc rễ
- Phát triển ứng dụng Windows
- Tư vấn kỹ thuật và rà soát thiết kế
- Liên hệ với chúng tôi
Liên kết tham khảo
-
Microsoft Learn, PBT_APMSUSPEND event. Về đây là sự kiện tới ngay trước khi máy tính vào trạng thái tạm dừng; về việc ứng dụng được kỳ vọng hoàn tất việc cần để lưu dữ liệu; và về hệ thống cho khoảng 2 giây để xử lý thông báo này, với ứng dụng tiếp tục vượt quá đó có thể bị gián đoạn. ↩ ↩2
-
Microsoft Learn, System Power Management Events. Về việc hệ thống phát trước các thay đổi chế độ vận hành như ngủ; về PBT_APMSUSPEND được thông báo trước ngủ rảnh để bạn có thể chuẩn bị bằng cách đóng tệp và lưu dữ liệu; về tạm dừng khẩn cấp (pin cực thấp và tương tự) không cho thông báo trước; về việc xử lý thông điệp này được phép tối đa 2 giây mỗi ứng dụng và bị cắt sau hết thời gian chờ; và về việc mọi ứng dụng được thông báo khi thức dậy. ↩ ↩2 ↩3
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. Về việc này được gửi sau PBT_APMRESUMEAUTOMATIC trên lần thức dậy do người dùng hoặc khi đầu vào người dùng được phát hiện sau đó; về chỉ PBT_APMRESUMEAUTOMATIC được gửi cho lần thức dậy từ nguyên nhân bên ngoài như đánh thức từ xa; và về việc ứng dụng được kỳ vọng mở lại các tệp đã đóng lúc ngủ và chuẩn bị cho đầu vào người dùng. ↩ ↩2
-
Microsoft Learn, WM_POWERBROADCAST message. Về PBT_APMRESUMEAUTOMATIC luôn được gửi khi thức dậy, với PBT_APMRESUMESUSPEND cũng được gửi trên lần thức dậy từ đầu vào người dùng; về thông điệp này không phân biệt loại trạng thái nguồn thấp; về chi tiết chuyển trạng thái nguồn được ghi trong nhật ký sự kiện hệ thống; và về việc gọi SetThreadExecutionState để ngăn hệ thống vào trạng thái nguồn thấp. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, SetThreadExecutionState function (winbase.h). Về ES_SYSTEM_REQUIRED và ES_DISPLAY_REQUIRED có thể ức chế ngủ rảnh của hệ thống và tắt nguồn màn hình; và về việc tuyên bố ức chế liên tục bằng ES_CONTINUOUS rồi gỡ bằng cách gọi ES_CONTINUOUS một mình khi xong. ↩ ↩2
-
Microsoft Learn, Prepare software for modern standby. Về Desktop Activity Moderator (DAM) tạm dừng ứng dụng desktop ở giai đoạn đầu tiên của chuyển tiếp vào Modern Standby; và về hệ thống sau đó chuyển theo giai đoạn vào pha nguồn thấp và pha phục hồi, với chỉ các thành phần được phép chạy gián đoạn. ↩ ↩2
-
Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). Về đây là API đăng ký nhận thông báo tạm dừng/thức dậy, và về việc chỉ định DEVICE_NOTIFY_CALLBACK để ứng dụng không cửa sổ hoặc dịch vụ có thể nhận thông báo qua callback ngoài việc gửi thông điệp tới handle cửa sổ. ↩ ↩2
-
Microsoft Learn, PowerSetRequest function (winbase.h). Về việc có thể đặt loại yêu cầu như giữ thức hệ thống hoặc màn hình trên đối tượng yêu cầu nguồn tạo bằng PowerCreateRequest; về việc có thể gắn chuỗi lý do chẩn đoán; và về các yêu cầu nguồn đang mở có thể được liệt kê bằng powercfg /requests. ↩ ↩2
-
Microsoft Learn, Modern standby SleepStudy. Về báo cáo do powercfg /sleepstudy tạo cho phép bạn xem, theo từng khoảng Modern Standby, tiêu thụ nguồn, hoạt động, và lý do thức (nút nguồn, đầu vào người dùng, bộ hẹn giờ thức, và tương tự). ↩
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
DllMain và Loader Lock — Lý do thật sự bạn bị bảo "đừng làm gì trong khởi tạo DLL"
Vì sao bạn không được gọi LoadLibrary hoặc đồng bộ với luồng khác từ DllMain. Dựa trên nguồn gốc, bài viết giải thích cách loader lock tu...
"Không phản hồi" thực sự là gì — Cách Windows quyết định ứng dụng đã treo, và cách thiết kế ứng dụng không treo
"Không phản hồi" của Windows là cơ chế trong đó hệ điều hành phán rằng một cửa sổ chưa lấy thông điệp trong 5 giây và thay nó bằng cửa sổ...
API thread pool Win32 — Đồng thời mà không tạo luồng, với CreateThreadpoolWork
Bạn đang rải các lời gọi CreateThread khắp mã native? Bài viết này giải thích API thread pool Win32 được thiết kế lại từ Vista — bốn đối ...
Named pipe trong thực tiễn — IPC chuẩn của Windows, từ thiết kế đến bảo mật
Hướng dẫn thực tiễn về named pipe, cơ chế giao tiếp giữa các tiến trình chuẩn của Windows. Bài viết sắp xếp, từ nguồn gốc, việc chọn giữa...
Thức tỉnh giả — Vì sao biến điều kiện thức "mà không được thông báo" và cách chờ đúng trên Windows
Lần chờ của biến điều kiện có thể trở về ngay cả khi không có thông báo nào tới (thức tỉnh giả). Bài viết này giải thích, từ triển khai W...
Chủ đề liên quan
Các trang này đặt chủ đề trong bối cảnh rộng hơn của dịch vụ và quyết định.
Chủ đề kỹ thuật Windows
Cổng vào phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Dịch vụ liên quan đến chủ đề này
Bài viết liên quan trực tiếp đến các dịch vụ sau.
Phát triển ứng dụng Windows
Ứng dụng nghiệp vụ, tích hợp thiết bị và công cụ liên lạc, từ yêu cầu đến phát triển.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- Ứng dụng có thể biết trước về ngủ và từ chối không?
- Trên Windows hiện tại bạn nhận được thông báo, nhưng không thể từ chối. Ngay trước khi ngủ, một thông điệp WM_POWERBROADCAST gửi sự kiện PBT_APMSUSPEND, và ở đây bạn có thể chuẩn bị bằng cách đóng tệp và lưu trạng thái, nhưng thời gian được phép xử lý khoảng 2 giây mỗi ứng dụng, và nếu vượt quá thì hệ thống tiếp tục mà không chờ. Trên một lần tạm dừng khẩn cấp như pin cực thấp, bản thân thông báo trước không tới. Vì vậy thiết kế "phải xong trước khi ngủ" không đứng vững; bạn cần thiết kế có thể phục hồi khi thức dậy dù lần cắt tới lúc nào. Với một khoảng việc bạn thật sự không muốn bị ngủ xuyên qua, hãy ức chế ngủ tường minh bằng SetThreadExecutionState hoặc một yêu cầu nguồn (PowerSetRequest).
- Làm sao phát hiện máy đã thức dậy?
- Nếu ứng dụng có cửa sổ, xử lý WM_POWERBROADCAST. Khi thức dậy từ tạm dừng, PBT_APMRESUMEAUTOMATIC tới, và nếu lần thức dậy do hành động của người dùng (nút nguồn hoặc nhấn phím), PBT_APMRESUMESUSPEND theo sau. Một lần thức dậy không có người dùng rồi ngủ lại ngay chỉ gửi PBT_APMRESUMEAUTOMATIC, nên cách tách cơ bản là đặt việc bắt buộc như kết nối lại về phía PBT_APMRESUMEAUTOMATIC và việc hướng tới người dùng như cập nhật màn hình về phía PBT_APMRESUMESUSPEND. Dịch vụ không cửa sổ và ứng dụng console có thể nhận cùng thông báo qua callback bằng RegisterSuspendResumeNotification với DEVICE_NOTIFY_CALLBACK.
- Tôi có thể giữ ứng dụng chạy trong lúc ngủ không?
- Theo quy tắc, không. Trong lúc ngủ, bản thân thực thi CPU dừng (trên máy Modern Standby, ứng dụng desktop bị tạm dừng bởi Desktop Activity Moderator), và mã của ứng dụng không chạy. Có hai lựa chọn. Một là ức chế ngủ chỉ khi việc đang tiến hành. Chỉ định ES_SYSTEM_REQUIRED với SetThreadExecutionState, hoặc phát yêu cầu nguồn bằng PowerCreateRequest/PowerSetRequest, sẽ ức chế ngủ rảnh tự động trong khoảng đó (bạn xác nhận bằng powercfg /requests). Điều đó vẫn không chặn được hành động ngủ tường minh như người dùng đóng nắp, nên bạn vẫn phải sẵn sàng cho thức dậy ngay cả khi đang ức chế. Cách kia là chấp nhận ngủ và thiết kế "bắt kịp sau khi thức dậy". Với việc theo lịch như batch ban đêm, bạn cũng có thể đánh thức PC bằng "Wake the computer to run this task" của Task Scheduler. Việc thật sự cần chạy liên tục thuộc về máy chủ hoặc dịch vụ được cấu hình không ngủ.
- Vì sao kết nối TCP và cổng serial ngừng hoạt động sau khi thức dậy?
- Vì bộ điều hợp mạng và thiết bị USB cũng rơi vào trạng thái nguồn thấp trong lúc ngủ. Kết nối TCP đã bị bên kia hoặc NAT hay tường lửa hủy vì hết thời gian chờ, và gửi/nhận sau khi thức dậy trả về lỗi (bạn thường không nhận ra cho tới khi nó lỗi). Bộ chuyển USB-sang-serial và tương tự đôi khi được coi như tháo rồi cắm lại thiết bị khi thức dậy, và handle bạn đã mở trở nên không hợp lệ. Với cả hai, giả định đúng là "handle và kết nối không sống sót qua lần thức dậy", và câu trả lời đúng là triển khai logic kết nối lại xây lại kết nối khi có thông báo thức dậy hoặc lỗi giao tiếp. Kết hợp keepalive định kỳ với thử lại dùng lùi mũ khi thất bại là mẫu đã ổn định.
- Làm sao điều tra ngủ bất ngờ hoặc thức dậy bất ngờ?
- Lệnh powercfg là công cụ đầu tiên. Theo hướng "nó không chịu ngủ", powercfg /requests liệt kê tiến trình và driver nào đã phát yêu cầu nguồn đang chặn ngủ. Theo hướng "nó tự thức", powercfg /lastwake cho lý do thức gần nhất và powercfg /waketimers cho các bộ hẹn giờ hiện đang được giữ chỗ để đánh thức máy. Trên máy Modern Standby, powercfg /sleepstudy tạo báo cáo tiêu thụ và hoạt động trong lúc ngủ. Lịch sử ngủ và thức dậy cũng được ghi trong nhật ký sự kiện (nguồn Kernel-Power trong nhật ký System), nên bạn có thể xác nhận trên dòng thời gian "khi nào nó ngủ, và khi nào cùng vì sao nó thức".