יישומים שנשברים בחזרה משינה — איך אירועי צריכת החשמל של Windows עובדים ואיך לבנות יישומים עסקיים ששורדים אותם
· Go Komura · Windows, ניהול צריכת חשמל, פיתוח Windows, יישומים עסקיים, בקרת התקנים, חקירת תקלות, Win32 API
“סגרתי את המחשב הנייד, פתחתי אותו בבוקר למחרת, והיישום העסקי היה מלא שגיאות.” “יישום הניטור של הציוד מאבד נתונים רק אחרי הצהריים.” “הכלי התושב שמייצא ל-Excel לפעמים נעצר בשגיאת חיבור.” — לכרטיסים האלה יש חשוד אחד משותף. שינה.
יישומים עסקיים מתקופת המחשבים השולחניים נכתבו על ההנחה הסמויה ש”המחשב נשאר דולק”. שדה הקרב העיקרי היום הוא המחשב הנייד, וכברירת מחדל הוא נרדם אחרי כמה דקות של חוסר פעילות. במחשב עם Modern Standby, סמנטיקת השינה עצמה השתנתה מהמודל המסורתי. מיועד למפתחות ולמפתחים שכותבים יישומים עסקיים ותוכנות בקרת ציוד ב-Windows, המאמר הזה מסדר ממקורות ראשוניים מה מערכת ההפעלה מודיעה ליישום לפני שינה ואחריה, מה נשבר, ואיך לכתוב יישום ששורד חזרה.
1. השורה התחתונה קודם
- שינה היא אירוע שליישום “אין זכות לסרב” לו. מקבלים הודעה ממש לפני עם
WM_POWERBROADCAST(PBT_APMSUSPEND), אבל תקופת החסד היא כשתי שניות, ובהשהיית חירום ההודעה אפילו לא מגיעה.12 - בחזרה מהשהיה מגיע PBT_APMRESUMEAUTOMATIC, ובחזרה ביוזמת משתמש מגיע גם PBT_APMRESUMESUSPEND. עבודה הכרחית כמו חיבור מחדש שייכת ככלל לראשון. כניסות ויציאות ממצב הסרק הנמוך של Modern Standby לא תמיד מתיישרות עם ההודעות האלה, לכן מתייחסים להודעות כעזר.34
- מתכננים בהנחה שחיבורי TCP, פורטים טוריים ו-handles של התקנים לא שורדים מעבר לחזרה. לוגיקת חיבור מחדש שבונה אותם בהודעת חזרה או בשגיאת תקשורת היא האירוע המרכזי.
- שימו לב לטיימרים ולטיפול בזמן. עבודה מחזורית נעצרת בשינה, ואיך היא נורה מיד אחרי חזרה תלוי ב-API הטיימר ובסביבת הריצה. גם “קפיצה ענקית בזמן שחלף” קורית, לכן הגישה הבטוחה היא לבנות מחדש את לוח הזמנים בחזרה.
- מדכאים שינה במפורש במרווחים שלא רוצים שישנו בהם. משתמשים ב-
SetThreadExecutionState(ES_SYSTEM_REQUIRED) או בבקשת צריכת חשמל (PowerSetRequest), ומבטלים תמיד כשהעבודה מסתיימת.45 - במחשב Modern Standby המערכת עדיין רצה לסירוגין בשינה, אבל יישומי שולחן העבודה מושהים. אי אפשר להחזיק את הציפייה ש”היישום שלנו אמור להמשיך לרוץ בשינה”.6
- כלי החקירה הסטנדרטיים הם
powercfg(/requests, /lastwake, /sleepstudy) ו-Kernel-Power ביומן האירועים.
2. מה קורה סביב שינה — זרימת אירועי צריכת החשמל
מערכת ההפעלה משדרת שינויי מצב צריכת חשמל לכל יישום כהודעת WM_POWERBROADCAST.2 שלושה אירועים עיקריים מעורבים בשינה ובחזרה.
| אירוע | משמעות |
|---|---|
| PBT_APMSUSPEND | עומד להיכנס לשינה (ההזדמנות האחרונה להתכונן) |
| PBT_APMRESUMEAUTOMATIC | חזר (תמיד מגיע בחזרה) |
| PBT_APMRESUMESUSPEND | חזרה שנגרמה מפעולת משתמש (זה מותנה) |
PBT_APMSUSPEND הוא ההודעה ממש לפני שינה, וכאן אפשר להתכונן בסגירת קבצים ושמירת מצב. יש שני תנאים. ראשית, הזמן המותר לעיבוד הוא כשתי שניות לכל יישום, ואם חורגים המערכת ממשיכה בלי לחכות.1 שנית, בהשהיית חירום כמו סוללה קריטית, הוא נרדם מיד בלי הודעה מוקדמת.2 תכנון ש”חייב להסתיים לפני השינה” לא מחזיק. מתייחסים להודעה כהזדמנות “לעשות אם מספיקים”, ואת העבודה העיקרית שמים בצד החזרה.
צד החזרה הוא שני שלבים. PBT_APMRESUMEAUTOMATIC מגיע בחזרה ממעבר השהיה. מעליו, אם המחשב חזר בגלל פעולת משתמש כמו כפתור ההפעלה או הקשה (או שנכחה משתמש זוהתה אחר כך), מגיע PBT_APMRESUMESUSPEND. לעומת זאת, חזרה ללא השגחה להשכמה מרחוק ברשת או לתחזוקה מעבירה רק PBT_APMRESUMEAUTOMATIC.3 שני השלבים האלה עצמם הם רמז לפיצול העבודה — עושים התאוששות מכנית כמו בניית חיבורים מחדש ב-PBT_APMRESUMEAUTOMATIC, ופעולות שפונות למשתמש כמו עדכון מסך או בקשת התחברות מחדש ב-PBT_APMRESUMESUSPEND.
sequenceDiagram
accTitle: זרימת ההודעות לשינה ולחזרה
accDescr: PBT_APMSUSPEND מגיע ממש לפני שינה עם כשתי שניות חסד; בחזרה PBT_APMRESUMEAUTOMATIC תמיד מגיע, ו-PBT_APMRESUMESUSPEND בא אחריו רק בחזרה ביוזמת משתמש
participant OS as OS
participant A as יישום
OS->>A: PBT_APMSUSPEND (כשתי שניות חסד)
A->>A: שמירת מצב וסגירת חיבורים
Note over OS: שינה (הקוד לא רץ)
OS->>A: PBT_APMRESUMEAUTOMATIC (מגיע בחזרה)
A->>A: חיבור מחדש ושחזור מצב
OS->>A: PBT_APMRESUMESUSPEND (רק בחזרת משתמש)
A->>A: עדכון מסך ועבודה שפונה למשתמש
איור 1: ההודעות הן רק “מילה אחת ממש לפני, ומילה או שתיים אחרי החזרה”. כוכב ההתאוששות הוא העבודה בצד החזרה.
flowchart TB
accTitle: ההבדל בין שינה רגילה להשהיית חירום
accDescr: שינה רגילה מעבירה PBT_APMSUSPEND ממש לפני עם כשתי שניות להתכונן, אבל השהיית חירום כמו סוללה קריטית עוצרת בלי הודעה מוקדמת, ולכן תכנון שתלוי בהודעה המוקדמת לא מחזיק
n2["שינה רגילה"] --> pre["PBT_APMSUSPEND (כשתי שניות חסד)"]
pre --> s1["הכנה, ואז עצירה"]
e2["השהיית חירום (סוללה קריטית)"] --> s2["עצירה בלי הודעה מוקדמת"]
s2 -.-> l2["תכנון שמניח שההודעה תגיע לא מחזיק"]
איור 2: השהיית חירום באה בלי אזהרה. לכן ההכנה היא “בונוס אם מספיקים”, והעבודה העיקרית הולכת לצד החזרה.
שימו לב ש-WM_POWERBROADCAST לא מבדיל את סוג מצב צריכת החשמל הנמוכה (שינה מול היברנציה).4 ההפשטה הנכונה ליישום היא לטפל בזה כסוג אחד של אירוע: “עצר, וחזר”. שירותים בלי חלון ויישומי קונסול יכולים לקבל את אותן הודעות עם RegisterSuspendResumeNotification בצורת callback (DEVICE_NOTIFY_CALLBACK).7
flowchart TB
accTitle: פיצול העבודה בין שני שלבי החזרה
accDescr: שמים התאוששות מכנית כמו חיבור מחדש ב-PBT_APMRESUMEAUTOMATIC שמגיע בחזרה; שמים עבודה שפונה למשתמש כמו עדכון מסך או בקשת התחברות מחדש ב-PBT_APMRESUMESUSPEND שמגיע רק בחזרה ביוזמת משתמש
ra["PBT_APMRESUMEAUTOMATIC (בחזרה)"] --> m["התאוששות מכנית"]
rs["PBT_APMRESUMESUSPEND (חזרת משתמש)"] --> u["עבודה שפונה למשתמש"]
m -.-> m1["חיבור מחדש ופתיחה מחדש של handles"]
u -.-> u1["עדכון מסך ובקשת התחברות מחדש"]
איור 3: האחרון לא מגיע בחזרה ללא השגחה, ולכן שמים התאוששות הכרחית עליו יחמיצו אותה.
3. Modern Standby — משמעות ה”שינה” השתנתה
עובדה מודרנית נוספת שצריך לקבל היא Modern Standby. שינת S3 המסורתית הייתה מודל פשוט ש”עצר את המערכת כולה”; שינה במחשב Modern Standby היא מודל דמוי סמארטפון שבו המערכת ממשיכה לרוץ לסירוגין אחרי שהמסך כבה.
מה שחשוב ליישום עסקי כאן הוא שיישומי שולחן העבודה מושהים על ידי Desktop Activity Moderator (DAM) בשלב הראשון של הכניסה לשינה.6 המערכת עצמה עדיין רצה מדי פעם כדי לשמור את הרשת ולקבל הודעות, אבל הרכיבים שמרוויחים מזה הם אלה שמשתתפים במנגנון הזה — קוד יישום שולחן עבודה רגיל לא רץ. לכן מנקודת מבט של מפתח המסקנה זהה ל-Modern Standby ול-S3 — מתכננים בהנחה שהקוד שלכם לא רץ בשינה.
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 וחומות אש מתייחסים לשתיקה שלכם כפקיעת זמן וזורקים את החיבור. גרוע מזה, הסוקט בצד הזה לא יודע על השגיאה, ולכן הוא נכשל רק כששולחים או מקבלים אחרי חזרה. או גרוע עוד יותר, המתנת קבלה לא נכשלת בכלל (לכן צריך keepalive). לחיבורי מסד נתונים ול-WebSockets יש אותה צורה.
Handles של פורט טורי והתקן USB הופכים ללא-תקפים. התקן מחובר ב-USB יכול להיראות, בחזרה, כאילו “נותק וחובר שוב” פעם אחת, וה-handle שהיה פתוח מתחיל להחזיר שגיאות. זה הדפוס הטיפוסי של יישום בקרת ציוד ש”מקבל שגיאת תקשורת רק אחרי הצהריים”. תכנון חיבור מחדש מכוסה גם במאמר התקשורת הטורית.
רציפות הזמן נשברת. עבודה מונעת-טיימר כמו “סקר כל 10 שניות” לא נורה בשינה. איך היא נורה מיד אחרי חזרה (עבודה שפגה נורה פעם אחת מיד, שום דבר לא קורה עד המחזור הבא, וכן הלאה) תלוי ב-API הטיימר ובסביבת הריצה, לכן אל תשארו את הטיפול בטיקים שהוחמצו להתנהגות מרומזת — הגישה הבטוחה היא לבנות מחדש את לוח הזמנים בהודעת החזרה. גם חישובי זמן שחלף (ההפרש מהחותמת הקודמת) הופכים פתאום ל”שווי 8 שעות”, וחישובי ממוצע או שיפוטי פקיעת זמן נשברים. עבודה מתוזמנת כמו “לרוץ כל לילה ב-02:00” פשוט לא רצה אם המחשב ישן באותה שעה (מעירים עם תכונת ההשכמה משינה של Task Scheduler אם צריך).
flowchart TB
accTitle: שלוש צורות שבהן רציפות הזמן נשברת
accDescr: עבודה מחזורית נעצרת בשינה והירי אחרי חזרה שונה לפי API, לכן בונים מחדש את לוח הזמנים בחזרה; ההפרש מהחותמת הקודמת הופך ענק אחרי חזרה, לכן שומרים עליו; עבודה מתוזמנת לא רצה אם המחשב ישן, לכן שוקלים השכמה משינה של Task Scheduler
t1["עבודה מחזורית: נעצרת"] -.-> g1["בנייה מחדש של לוח הזמנים"]
t2["זמן שחלף: מתנפח"] -.-> g2["שמירה מפני הפרשים חריגים"]
t3["מתוזמן: לא רץ"] -.-> g3["השכמה משינה"]
g1 ~~~ t2
g2 ~~~ t3
איור 5: כותבים טיפול בטיימר ובזמן בהנחה ש”הזמן קופץ”. לכל אחת משלוש הצורות יש סוג נגד-מידה.
flowchart TB
accTitle: שלושה דברים שנשברים מעבר לשינה
accDescr: מעבר לשינה, חיבור TCP נזרק בפקיעת זמן בצד השני, handle של התקן USB מתבטל כחיבור מחדש, ועבודה מבוססת-זמן-שחלף רואה קפיצת זמן ענקית. מתאוששים בכל אחד עם חיבור מחדש, פתיחה מחדש ושמירת הפרש
sleep["מרווח השינה"] --> tcp["TCP: העמית זרק אותו"]
sleep --> more{"USB או זמן שחלף?"}
more --> usb["USB: handle לא תקף"]
more --> time["זמן שחלף: קפיצה"]
tcp -.-> r1["זיהוי + חיבור מחדש"]
usb -.-> r2["פתיחה מחדש של ההתקן"]
time -.-> r3["שמירה מפני הפרשים חריגים"]
איור 6: מה שנשבר נופל לשלוש משפחות — “חיבורים”, “handles” ו”רציפות הזמן” — ולכל אחת יש סוג התאוששות מיושב.
אימות מחדש למשאבים משותפים. כונני רשת ו-VPN לעיתים קרובות צריכים להיווצר מחדש אחרי חזרה, ויש “עמק הפעלה” של כמה עד כמה עשרות שניות מיד אחרי חזרה שבו גישה נכשלת. בטוח יותר לא לנסות הכול בבת אחת מיד אחרי חזרה, אלא לחכות קצת ולנסות בשלבים.
5. בניית יישומים ששורדים חזרה
העיקרון הוא דבר אחד. מניחים ש”חיבורים ו-handles לא שורדים מעבר לשינה”, ומבנים את היישום כך שתמיד אפשר להתאושש.
מזהים חזרה ומתאוששים. כש-WM_POWERBROADCAST של החלון העליון מקבל PBT_APMRESUMEAUTOMATIC, זורקים את החיבורים שמחזיקים ובונים אותם מחדש. הנקודה היא לא לסמוך על הודעת החזרה לבדה. הודעות שהוחמצו ותקשורת שקורה לפני ההודעה הן שתיהן אמיתיות, לכן תמיד מצמידים נתיב ש”מתחבר מחדש כשמזוהה שגיאת תקשורת”, ומתייחסים להודעת החזרה כטריגר שרק מתחיל את זה מוקדם יותר.
// 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);
}
עושים את עבודת החיבור מחדש עצמה אידמפוטנטית (בטוחה לא משנה כמה פעמים קוראים לה), מנסים שוב בכשל עם exponential backoff, ובמצב יציב מזהים חיבור מת מוקדם עם keepalive — שמים את השלושה כמערכת, ותשרדו לא רק חזרה משינה אלא גם נפילת רשת קצרה או אתחול התקן.
flowchart TB
accTitle: תכנון חיבור מחדש עמיד לחזרה
accDescr: הודעת החזרה, שגיאת תקשורת וכשל keepalive כולם נשפכים לאותה עבודת חיבור מחדש אידמפוטנטית, שמנסה שוב עם exponential backoff בכשל
e1["הודעת חזרה (PBT_APMRESUMEAUTOMATIC)"] --> r["עבודת חיבור מחדש אידמפוטנטית"]
e2["זיהוי שגיאת תקשורת"] --> r
e3["כשל keepalive"] --> r
r --> ok{"הצליח?"}
ok -->|"כן"| run["חזרה לפעולה רגילה"]
ok -->|"לא"| back["ניסיון חוזר אחרי exponential backoff"]
back --> r
איור 7: מרכזים חיבור מחדש לנתיב אידמפוטנטי אחד, ונכנסים לאותו כביש מהודעת החזרה, מזיהוי שגיאה או מה-keepalive.
בודקים מחדש את הטיפול בזמן. לעבודה שמשתמשת ב”זמן שחלף מאז הפעם הקודמת”, שמים שמירה שמבטלת את המרווח כשהיא מזהה הפרש גדול באופן חריג (לא מקפלים לממוצע, לא מתייחסים כפקיעת זמן). מדידת זמן שחלף מעבר לחזרה דורשת להחזיק הבחנה בין שעון שמתקדם בשינה (זמן קיר) לבין זמן שבאמת הושקע בעבודה.
מדכאים שינה במפורש במרווחים שלא רוצים שישנו בהם. במהלך עבודה שאסור שישנו באמצעה — העברת נתונים, תקשורת רציפה עם התקן וכן הלאה — אפשר להשאיר את המערכת ערה עם SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (מוסיפים ES_DISPLAY_REQUIRED אם רוצים גם להשאיר את המסך דולק).45 שיטה מנומסת יותר היא ה-API של בקשות צריכת חשמל (PowerCreateRequest + PowerSetRequest), שיכולה לצרף מחרוזת סיבה, ו-powercfg /requests יציג אז “מי חוסם ולמה”.8 שימו לב שדיכוי דרך SetThreadExecutionState הוא לפי תהליכון, ומבטלים אותו מאותו תהליכון שהגדיר אותו. לעבודה שמחליפה תהליכונים, כמו async/await, משתמשים בצד בקשת צריכת החשמל, שמנוהל לפי handle. יש זהירויות. ראשית, מה שהן מדכאות הוא שינת סרק אוטומטית. הן לא יכולות לעצור פעולת משתמש מפורשת כמו סגירת המכסה או בחירת שינה מתפריט התחלה, ולכן אי אפשר לדלג על תכנון החיבור מחדש של הפרק הזה גם בזמן דיכוי. שנית, בסוללה במחשב Modern Standby, גם בקשות צריכת החשמל האלה נחתכות זמן מה אחרי שפקיעת זמן השינה חולפת. עבודה שאי אפשר להפריע לה חייבת להיות מובטחת בחשמל AC או בתפעול.8 שלישית, תמיד מבטלים כשהעבודה מסתיימת. ביטול שהוחמץ הופך לבאג חדש: “המחשב הזה, משום מה, לא נרדם”.
flowchart TB
accTitle: שני אמצעים לדיכוי שינה
accDescr: בין אם משתמשים ב-SetThreadExecutionState הנוח או ב-API של בקשת צריכת חשמל שיכול לצרף מחרוזת סיבה ונראה למנהל דרך powercfg, תמיד מבטלים כשהעבודה מסתיימת
need["מרווח עבודה שאסור שישנו בו"] --> a["SetThreadExecutionState"]
need --> b["בקשת צריכת חשמל (PowerSetRequest)"]
a -.-> a1["נוח — רק דגלים"]
b -.-> b1["עם סיבה — נראה ב-powercfg"]
a --> off["תמיד מבטלים כשהעבודה מסתיימת"]
b --> off
איור 8: לשני האמצעים, “מבטלים כשמסיימים” הוא תנאי מוחלט. בקשת צריכת חשמל שיכולה להראות את הסיבה נדיבה יותר לתפעול.
שירותים ויישומים בלי חלון מקבלים הודעות callback עם RegisterSuspendResumeNotification (DEVICE_NOTIFY_CALLBACK).7 אם פעולה רציפה היא דרישה אמיתית, הפתרון השורשי הוא לבדוק מחדש תכנון שמשאיר את העבודה תושבת במחשב לקוח שנרדם, ולהעביר אותה לצד השרת או למחשב שמופעל בלי שינה.
6. חקירה — powercfg ויומן האירועים
חקירה סביב צריכת חשמל נעזרת היטב בכלים שמגיעים עם מערכת ההפעלה.
- לא נרדם:
powercfg /requestsמציג את התהליכים והמנהלים שהנפיקו בקשת צריכת חשמל. “היישום שכח לבטלSetThreadExecutionState” מופיע כאן גם. - מתעורר מעצמו:
powercfg /lastwakeמציג את סיבת ההשכמה האחרונה, ו-powercfg /waketimersמציג טיימרים ששמורים כרגע להעיר את המחשב. - איכות Modern Standby:
powercfg /sleepstudyמייצר דוח צריכת חשמל ופעילות לכל מרווח שינה.9 - אישור ציר הזמן: מקור Kernel-Power ביומן האירועים (System) שומר רשומות של כניסה לשינה ושל חזרה. התאמה מול יומן היישום מאפשרת לאשר באופן אובייקטיבי האם “הייתה חזרה ממש לפני השגיאה”.
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: סימפטומים ממופים לפקודות חקירה בשלוש משפחות. קודם מאשרים “האם זה עתה ישן”, ואז מפצלים.
בטיפול בכרטיסים, עצם השאלה הראשונה “האם המחשב ישן ממש לפני (האם סגרו את המכסה)” מאיצה מאוד את הבידוד.
7. סיכום
- אי אפשר לסרב לשינה. ההודעה המוקדמת (PBT_APMSUSPEND) היא best-effort עם כשתי שניות חסד, והיא לא מגיעה בחירום. שמים את התכנון העיקרי בצד החזרה.
- הודעות החזרה הן PBT_APMRESUMEAUTOMATIC (בחזרה מהשהיה) + PBT_APMRESUMESUSPEND (בפעולת משתמש). משאירים חיבור מחדש מונע-שגיאה בנתיב הראשי למקרה שההודעה לא מגיעה.
- מניחים שחיבורים ו-handles לא שורדים מעבר לחזרה, ומממשים את הסט התלת-חלקי של חיבור מחדש אידמפוטנטי + exponential backoff + keepalive.
- שמים שמירה מפני “הפרשים חריגים” על עבודה מבוססת-זמן-שחלף. מתכננים עבודה מתוזמנת בהנחה שהיא לא רצה בשינה.
- במרווחים שאסור שישנו בהם, מדכאים שינה במפורש עם
SetThreadExecutionStateאו בקשת צריכת חשמל, ותמיד מבטלים כשמסיימים. - החקירה היא
powercfg(/requests, /lastwake, /sleepstudy) ויומן האירועים Kernel-Power. בטיפול בכרטיסים, שואלים קודם “האם זה עתה ישן”.
מנקודת מבט של היישום, שינה היא אירוע שבו “הזמן קופץ בלי אזהרה, החיבורים לסביבה נחתכים, ואז זה חוזר”. האם ארגתם את זה לתוך התכנון כחלק מחיי היומיום, ולא כמצב חריג, הוא מה שמפריד את היציבות של יישום עסקי בעידן המחשב הנייד.
מאמרים קשורים
- מלכודות יישום תקשורת טורית — דרך חיבור מחדש ותכנון יומנים
- כיבוי Windows כפי שנראה מהיישום — לשרוד נכון הודעות יציאה, אתחולים ואובדן חשמל
- מהו מצב היעילות של Windows? — סמל העלה הירוק ואיך לכבות אותו
- למה להעדיף המתנות אירוע על Sleep(1) ב-Windows
- מה “לא מגיב” באמת — איך Windows מחליט שיישום נתקע, ואיך לתכנן יישומים שלא
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בחקירות שורש של באגים כמו “התקשורת נשברת אחרי חזרה משינה” ו”החיבור להתקן נופל אחרי הצהריים”, בהתאמת לוגיקת חיבור מחדש וטיפול באירועי צריכת חשמל ליישומים קיימים, ובסקירות תכנון של יישומים עסקיים ותוכנות בקרת ציוד שמניחות הפעלת מחשב נייד.
קישורים
-
Microsoft Learn, PBT_APMSUSPEND event. על כך שזה האירוע שמגיע ממש לפני שהמחשב נכנס למצב השהיה; על כך שהיישום אמור לסיים את העבודה הנדרשת לשמירת נתונים; ועל כך שהמערכת מאפשרת כשתי שניות לטפל בהודעה זו, ויישום שממשיך מעבר לזה נתון להפרעה. ↩ ↩2
-
Microsoft Learn, System Power Management Events. על שידור מוקדם של שינויי מצב הפעלה כמו שינה; על כך ש-PBT_APMSUSPEND מגיע לפני שינת סרק כדי שאפשר יהיה להתכונן בסגירת קבצים ושמירת נתונים; על כך שהשהיית חירום (סוללה קריטית וכדומה) לא נותנת הודעה מוקדמת; על כך שטיפול בהודעה זו מותר לכל היותר כשתי שניות ליישום ונחתך אחרי פקיעת הזמן; ועל כך שכל יישום מקבל הודעה בחזרה. ↩ ↩2 ↩3
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. על כך שזה נשלח אחרי PBT_APMRESUMEAUTOMATIC בחזרה ביוזמת משתמש או כשקלט משתמש מזוהה אחר כך; על כך שנשלח רק PBT_APMRESUMEAUTOMATIC בחזרה מסיבה חיצונית כמו השכמה מרחוק; ועל כך שהיישום אמור לפתוח מחדש קבצים שנסגרו בשינה ולהתכונן לקלט משתמש. ↩ ↩2
-
Microsoft Learn, WM_POWERBROADCAST message. על כך ש-PBT_APMRESUMEAUTOMATIC תמיד נשלח בחזרה, ו-PBT_APMRESUMESUSPEND נשלח גם בחזרה מקלט משתמש; על כך שההודעה לא מבדילה את סוג מצב צריכת החשמל הנמוכה; על כך שפרטי מעברי מצב צריכת חשמל נרשמים ביומן האירועים של המערכת; ועל קריאה ל-SetThreadExecutionState כדי למנוע כניסה למצב צריכת חשמל נמוכה. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, SetThreadExecutionState function (winbase.h). על כך ש-ES_SYSTEM_REQUIRED ו-ES_DISPLAY_REQUIRED יכולים לדכא שינת סרק וכיבוי תצוגה; ועל הכרזת דיכוי רציף עם ES_CONTINUOUS וביטולו בקריאה ל-ES_CONTINUOUS לבדו כשמסיימים. ↩ ↩2
-
Microsoft Learn, Prepare software for modern standby. על כך ש-Desktop Activity Moderator (DAM) משהה יישומי שולחן עבודה בשלב הראשון של המעבר ל-Modern Standby; ועל כך שהמערכת אחר כך נעה בשלבים לשלב צריכת חשמל נמוכה ולשלב חוסן, ורק רכיבים מותרים רצים לסירוגין. ↩ ↩2
-
Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). על כך שזה ה-API שרושם לקבלת הודעות השהיה/חזרה, ועל ציון DEVICE_NOTIFY_CALLBACK כדי שיישום או שירות בלי חלון יוכלו לקבל את ההודעה דרך callback בנוסף למסירה לחלון. ↩ ↩2
-
Microsoft Learn, PowerSetRequest function (winbase.h). על היכולת להגדיר סוג בקשה כמו השארת מערכת או תצוגה ערות על אובייקט בקשת צריכת חשמל שנוצר עם PowerCreateRequest; על היכולת לצרף מחרוזת סיבה אבחנתית; ועל כך שבקשות צריכת חשמל פתוחות ניתנות למנייה עם powercfg /requests. ↩ ↩2
-
Microsoft Learn, Modern standby SleepStudy. על כך שהדוח שמייצר powercfg /sleepstudy מאפשר לבדוק, לכל מרווח Modern Standby, צריכת חשמל, פעילות וסיבת ההשכמה (כפתור הפעלה, קלט משתמש, טיימר השכמה וכן הלאה). ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
DllMain ונעילת הטוען — הסיבה האמיתית שאומרים לכם "לא לעשות כלום באתחול DLL"
למה אסור לקרוא ל-LoadLibrary או להסתנכרן עם תהליכונים אחרים מתוך DllMain. המאמר מסביר ממקורות ראשוניים איך נעילת הטוען מסדרת כל הודעת DLL...
מה "לא מגיב" באמת — איך Windows מחליט שיישום נתקע, ואיך לתכנן יישומים שלא
"לא מגיב" של Windows הוא מנגנון שבו מערכת ההפעלה קובעת שחלון לא שלף הודעה במשך 5 שניות ומחליפה אותו בחלון רפאים. המאמר מכסה את פנים השיפו...
Win32 Thread Pool API — מקביליות בלי ליצור תהליכונים, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד הנייטיב? המאמר מסביר את ה-API של מאגר התהליכונים של Win32 שעוצב מחדש ב-Vista — ארבעת האובייקטים work,...
Named Pipes בפועל — ה-IPC הסטנדרטי של Windows, מתכנון ועד אבטחה
מדריך מעשי ל-Named Pipes, התקשורת הסטנדרטית בין תהליכים ב-Windows. המאמר מסדר ממקורות ראשוניים את הבחירה בין מצב בתים למצב הודעות, תכנון ...
התעוררויות ספונטניות — למה משתני תנאי מתעוררים "בלי שהודיעו להם" ואיך להמתין נכון ב-Windows
המתנה על משתנה תנאי יכולה לחזור גם כשלא הגיעה הודעה (התעוררות ספונטנית). המאמר מסביר ממימוש Windows למה המפרט מתיר זאת, ומציג את צורת ההמ...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- האם יישום יכול לדעת מראש על שינה ולסרב לה?
- ב-Windows הנוכחי אפשר לקבל את ההודעה, אבל אי אפשר לסרב. ממש לפני שינה, הודעת WM_POWERBROADCAST מעבירה אירוע PBT_APMSUSPEND, וכאן אפשר להתכונן בסגירת קבצים ושמירת מצב, אבל הזמן המותר לעיבוד הוא כשתי שניות לכל יישום, ואם חורגים המערכת ממשיכה בלי לחכות. בהשהיה חירום כמו סוללה קריטית, ההודעה המוקדמת עצמה לא מגיעה. לכן תכנון ש"חייב להסתיים לפני השינה" לא מחזיק; צריך תכנון שיכול להתאושש בחזרה, מתי שלא תהיה החיתוך. לקטע עבודה שבאמת לא רוצים שישנו באמצעו, מדכאים שינה במפורש עם SetThreadExecutionState או בקשת צריכת חשמל (PowerSetRequest).
- איך מזהים שהמחשב חזר משינה?
- אם ליישום יש חלון, מטפלים ב-WM_POWERBROADCAST. בחזרה מהשהיה מגיע PBT_APMRESUMEAUTOMATIC, ואם החזרה נגרמה מפעולת משתמש (כפתור ההפעלה או הקשה), אחריו מגיע PBT_APMRESUMESUSPEND. חזרה ללא השגחה שחוזרת מיד לשינה מעבירה רק PBT_APMRESUMEAUTOMATIC, לכן הפיצול הבסיסי הוא לשים עבודה הכרחית כמו חיבור מחדש בצד PBT_APMRESUMEAUTOMATIC ועבודה שפונה למשתמש כמו עדכון מסך בצד PBT_APMRESUMESUSPEND. שירותים בלי חלון ויישומי קונסול יכולים לקבל את אותן הודעות דרך callback עם RegisterSuspendResumeNotification ו-DEVICE_NOTIFY_CALLBACK.
- אפשר להשאיר את היישום רץ במהלך שינה?
- ככלל, לא. בשינה עצירת ביצוע ה-CPU עצמה (במחשב Modern Standby יישומי שולחן העבודה מושהים על ידי Desktop Activity Moderator), וקוד היישום לא רץ. יש שתי בחירות. אחת היא לדכא שינה רק בזמן שיש עבודה בתהליך. ציון ES_SYSTEM_REQUIRED ב-SetThreadExecutionState, או הנפקת בקשת צריכת חשמל עם PowerCreateRequest/PowerSetRequest, ידכאו שינת סרק אוטומטית באותו מרווח (אפשר לאשר ב-powercfg /requests). זה עדיין לא יכול לעצור פעולת שינה מפורשת כמו סגירת המכסה, ולכן גם בזמן דיכוי צריך להיות מוכנים לחזרה. השנייה היא לקבל שינה ולתכנן "להדביק אחרי החזרה". לעבודה מתוזמנת כמו אצווה לילית אפשר גם להעיר את המחשב עם "Wake the computer to run this task" של Task Scheduler. עבודה שבאמת צריכה לרוץ ברציפות שייכת לשרת או לשירות שמוגדר לא לישון.
- למה חיבורי TCP ופורטים טוריים מפסיקים לעבוד אחרי חזרה?
- כי גם מתאמי רשת והתקני USB יורדים למצב צריכת חשמל נמוכה בשינה. חיבור ה-TCP כבר נזרק על ידי הצד השני או על ידי פקיעת NAT או חומת אש, ושליחה/קבלה אחרי חזרה מחזירה שגיאה (לעיתים קרובות שמים לב רק כשיש שגיאה). מתאמי USB-לטורי וכדומה מטופלים לפעמים כהסרה והכנסה מחדש של התקן בחזרה, וה-handle שהיה פתוח הופך ללא-תקף. בשני המקרים ההנחה הנכונה היא ש"handles וחיבורים לא שורדים מעבר לחזרה", והתשובה הנכונה היא לממש לוגיקת חיבור מחדש שבונה את החיבור מחדש בהודעת חזרה או בשגיאת תקשורת. שילוב keepalive תקופתי עם ניסיונות חוזרים ב-exponential backoff בכשל הוא הדפוס המקובל.
- איך חוקרים שינה לא צפויה או חזרה לא צפויה?
- פקודת powercfg היא הכלי הראשון. בכיוון "לא נרדם", powercfg /requests מציג אילו תהליכים ומנהלים הנפיקו בקשת צריכת חשמל שחוסמת שינה. בכיוון "מתעורר מעצמו", powercfg /lastwake מציג את סיבת ההשכמה האחרונה ו-powercfg /waketimers מציג טיימרים ששמורים כרגע להעיר את המחשב. במחשב Modern Standby, powercfg /sleepstudy מייצר דוח צריכה ופעילות בשינה. היסטוריית שינה וחזרה נרשמת גם ביומן האירועים (מקור Kernel-Power ביומן System), כך שאפשר לאשר בציר זמן "מתי ישן, ומתי ולמה התעורר".