แอปที่พังเมื่อรีซูมจากสลีป — อีเวนต์พลังงานของ Windows ทำงานอย่างไร และสร้างแอปธุรกิจที่รอดได้อย่างไร

· · Windows, การจัดการพลังงาน, การพัฒนา Windows, แอปธุรกิจ, การควบคุมอุปกรณ์, การแก้ปัญหา, Win32 API

“ฉันปิดแล็ปท็อป เปิดเช้าวันรุ่งขึ้น แล้วแอปธุรกิจเต็มไปด้วยข้อผิดพลาด” “แอปเฝ้าอุปกรณ์ทิ้งข้อมูลเฉพาะหลังอาหารกลางวัน” “เครื่องมือประจำเครื่องที่ส่งออกไป Excel บางครั้งหยุดด้วยข้อผิดพลาดการเชื่อมต่อ” — ตั๋วเหล่านี้มีผู้ต้องสงสัยคนเดียว สลีป

แอปธุรกิจจากยุคที่พีซีตั้งโต๊ะเป็นกระแสหลักถูกเขียนบนสมมติฐานที่ไม่พูดออกมาว่า “พีซีเปิดค้างอยู่” สนามหลักวันนี้คือแล็ปท็อป และโดยค่าเริ่มต้นมันสลีปหลังว่างไม่กี่นาที บนเครื่องที่รองรับ Modern Standby ความหมายของสลีปเองก็เปลี่ยนจากโมเดลดั้งเดิม มุ่งไปที่นักพัฒนาที่เขียนแอปธุรกิจและซอฟต์แวร์ควบคุมอุปกรณ์บน Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิว่า OS แจ้งแอปอย่างไรก่อนและหลังสลีป อะไรพัง และจะเขียนแอปที่รอดตอนรีซูมได้อย่างไร

1. สรุปก่อนเลย

  • สลีปคืออีเวนต์ที่แอป “ไม่มีสิทธิ์ปฏิเสธ” คุณถูกแจ้งก่อนหน้าเล็กน้อยด้วย WM_POWERBROADCAST (PBT_APMSUSPEND) แต่ระยะผ่อนคือประมาณ 2 วินาที และในการระงับฉุกเฉินการแจ้งก็ไม่มาถึงด้วยซ้ำ12
  • เมื่อรีซูมจากการระงับ PBT_APMRESUMEAUTOMATIC จะมาถึง และเมื่อรีซูมที่ผู้ใช้เป็นผู้เริ่ม PBT_APMRESUMESUSPEND จะมาถึงด้วย งานที่จำเป็น เช่น เชื่อมต่อใหม่ อยู่ในฝั่งแรกเป็นกฎ การเข้าและออกจากโหมดว่างพลังงานต่ำของ Modern Standby ไม่ได้เรียงกับแจ้งเหล่านี้เสมอ ดังนั้นให้ถือการแจ้งเป็นเครื่องช่วย34
  • ออกแบบบนสมมติฐานว่าการเชื่อมต่อ TCP พอร์ตอนุกรม และแฮนเดิลอุปกรณ์ไม่รอดข้ามรีซูม ตรรกะเชื่อมต่อใหม่ที่สร้างพวกมันใหม่จากการแจ้งรีซูมหรือข้อผิดพลาดการสื่อสารคือเหตุการณ์หลัก
  • ระวังตัวตั้งเวลาและการจัดการเวลา งานเป็นระยะหยุดระหว่างสลีป และวิธีที่ยิงทันทีหลังรีซูมต่างกันตาม timer API และรันไทม์ “การกระโดดมหาศาลของเวลาที่ผ่านไป” ก็เกิดเช่นกัน ดังนั้นแนวทางที่ปลอดภัยคือสร้างตารางใหม่ตอนรีซูม
  • ยับยั้งสลีปอย่างชัดเจนสำหรับช่วงที่คุณไม่อยากให้สลีปผ่าน ใช้ SetThreadExecutionState (ES_SYSTEM_REQUIRED) หรือ power request (PowerSetRequest) และยกเลิกเสมอเมื่องานเสร็จ45
  • บนเครื่อง Modern Standby ระบบยังรันเป็นช่วง ๆ ระหว่างสลีป แต่แอปเดสก์ท็อปถูกพัก คุณถือความคาดหวังว่า “แอปของเราควรรันต่อไประหว่างสลีป” ไม่ได้6
  • เครื่องมือสอบสวนมาตรฐานคือ powercfg (/requests, /lastwake, /sleepstudy) และ Kernel-Power ในล็อกอีเวนต์

2. สิ่งที่เกิดรอบสลีป — ลำดับอีเวนต์พลังงาน

OS กระจายการเปลี่ยนสถานะพลังงานไปยังทุกแอปเป็นข้อความ WM_POWERBROADCAST2 มีอีเวนต์หลักสามอย่างที่เกี่ยวข้องกับสลีปและรีซูม

อีเวนต์ ความหมาย
PBT_APMSUSPEND กำลังจะเข้าสลีป (โอกาสสุดท้ายที่จะเตรียม)
PBT_APMRESUMEAUTOMATIC รีซูมแล้ว (มาถึงเสมอตอนรีซูม)
PBT_APMRESUMESUSPEND รีซูมที่เกิดจากการกระทำของผู้ใช้ (อันนี้มีเงื่อนไข)

PBT_APMSUSPEND คือการแจ้งก่อนสลีปไม่กี่ขณะ และที่นี่คุณเตรียมได้ด้วยการปิดไฟล์และบันทึกสถานะ แต่มีสองเงื่อนไข ประการแรก เวลาที่อนุญาตให้ประมวลผลคือประมาณ 2 วินาทีต่อแอป และหากเกินระบบจะเดินต่อโดยไม่รอ1 ประการที่สอง ในการระงับฉุกเฉิน เช่น แบตเตอรี่ต่ำวิกฤต มันสลีปทันทีโดยไม่มีการแจ้งล่วงหน้า2 การออกแบบที่ “ต้องทำให้เสร็จก่อนสลีป” ไม่ตั้งอยู่ได้ ให้ถือการแจ้งเป็นโอกาสที่จะ “ทำถ้าทัน” และวางงานหลักไว้ฝั่งรีซูม

ฝั่งรีซูมมีสองขั้น PBT_APMRESUMEAUTOMATIC มาถึงเมื่อรีซูมจากการเปลี่ยนไประงับ นอกจากนั้น หากเครื่องรีซูมเพราะการกระทำของผู้ใช้ เช่น ปุ่มเปิดปิดหรือการกดแป้น (หรือตรวจพบว่ามีผู้ใช้อยู่ในภายหลัง) PBT_APMRESUMESUSPEND จะตามมา ในทางกลับกัน การรีซูมแบบไม่มีผู้ดูแลเพื่อปลุกจากระยะไกลผ่านเครือข่ายหรือเพื่อบำรุงรักษาส่งเฉพาะ PBT_APMRESUMEAUTOMATIC3 สองขั้นนี้เองเป็นคำใบ้ว่าจะแยกงานอย่างไร — ทำการกู้คืนเชิงกล เช่น สร้างการเชื่อมต่อใหม่บน PBT_APMRESUMEAUTOMATIC และทำการกระทำที่หันหน้าหาผู้ใช้ เช่น อัปเดตหน้าจอหรือพร้อมต์ล็อกอินใหม่บน PBT_APMRESUMESUSPEND

ลำดับการแจ้งสำหรับสลีปและรีซูมPBT_APMSUSPEND มาถึงก่อนสลีปไม่กี่ขณะพร้อมระยะผ่อนประมาณ 2 วินาที เมื่อรีซูม PBT_APMRESUMEAUTOMATIC มาถึงเสมอ และ PBT_APMRESUMESUSPEND ตามมาเฉพาะเมื่อรีซูมที่ผู้ใช้เป็นผู้เริ่มแอปOSแอปOSสลีป (โค้ดไม่รัน)PBT_APMSUSPEND (ระยะผ่อนประมาณ 2 วินาที)บันทึกสถานะและปิดการเชื่อมต่อPBT_APMRESUMEAUTOMATIC (มาถึงตอนรีซูม)เชื่อมต่อใหม่และคืนสถานะPBT_APMRESUMESUSPEND (เฉพาะรีซูมที่ผู้ใช้เริ่ม)อัปเดตหน้าจอและงานที่หันหน้าหาผู้ใช้

ภาพ 1: การแจ้งเป็นเพียง “คำหนึ่งก่อนหน้าเล็กน้อย และอีกหนึ่งหรือสองคำหลังรีซูม” ดาวของการกู้คืนคืองานฝั่งรีซูม

ความต่างระหว่างสลีปธรรมดากับการระงับฉุกเฉินสลีปธรรมดาส่ง PBT_APMSUSPEND ก่อนหน้าเล็กน้อยพร้อมเวลาเตรียมประมาณ 2 วินาที แต่การระงับฉุกเฉิน เช่น แบตเตอรี่วิกฤต หยุดโดยไม่มีการแจ้งล่วงหน้า ดังนั้นการออกแบบที่พึ่งการแจ้งล่วงหน้าจึงไม่ตั้งอยู่ได้สลีปธรรมดาPBT_APMSUSPEND (ระยะผ่อนประมาณ 2 วินาที)เตรียม แล้วหยุดระงับฉุกเฉิน (แบตเตอรี่ต่ำวิกฤต)หยุดโดยไม่มีการแจ้งล่วงหน้าการออกแบบที่สมมติว่าการแจ้งจะมาไม่ตั้งอยู่ได้

ภาพ 2: การระงับฉุกเฉินมาโดยไม่มีคำเตือน ดังนั้นการเตรียมคือ “โบนัสถ้าทัน” และงานหลักไปอยู่ฝั่งรีซูม

โปรดทราบว่า WM_POWERBROADCAST ไม่แยกชนิดของสถานะพลังงานต่ำ (สลีปกับไฮเบอร์เนต)4 นามธรรมที่ถูกสำหรับแอปคือถือว่าเป็นอีเวนต์ชนิดเดียว: “มันหยุด แล้วมันกลับมา” บริการที่ไม่มีหน้าต่างและแอปคอนโซลรับการแจ้งเดียวกันได้ด้วยการใช้ RegisterSuspendResumeNotification ในรูปคอลแบ็ก (DEVICE_NOTIFY_CALLBACK)7

การแยกงานข้ามสองขั้นของรีซูมวางการกู้คืนเชิงกล เช่น เชื่อมต่อใหม่ บน PBT_APMRESUMEAUTOMATIC ซึ่งมาถึงตอนรีซูม วางงานที่หันหน้าหาผู้ใช้ เช่น อัปเดตหน้าจอหรือพร้อมต์ล็อกอินใหม่ บน PBT_APMRESUMESUSPEND ซึ่งมาถึงเฉพาะเมื่อรีซูมที่ผู้ใช้เป็นผู้เริ่มPBT_APMRESUMEAUTOMATIC (ตอนรีซูม)การกู้คืนเชิงกลPBT_APMRESUMESUSPEND (รีซูมที่ผู้ใช้เริ่ม)งานที่หันหน้าหาผู้ใช้เชื่อมต่อใหม่และเปิดแฮนเดิลใหม่อัปเดตหน้าจอและพร้อมต์ล็อกอินใหม่

ภาพ 3: อันหลังไม่มาถึงในการรีซูมแบบไม่มีผู้ดูแล ดังนั้นการวางการกู้คืนที่จำเป็นไว้ที่อันหลังจะพลาด

3. Modern Standby — ความหมายของ “สลีป” ได้เปลี่ยนไป

ข้อเท็จจริงสมัยใหม่อีกอย่างที่ต้องรับไว้คือ Modern Standby สลีป S3 แบบดั้งเดิมเป็นโมเดลเรียบง่ายที่ “หยุดระบบทั้งก้อน” สลีปบนเครื่อง Modern Standby เป็นโมเดลแบบสมาร์ตโฟนที่ระบบยังรันเป็นช่วง ๆ หลังหน้าจอดับ

สิ่งที่สำคัญสำหรับแอปธุรกิจที่นี่คือ แอปเดสก์ท็อปถูกพักโดย Desktop Activity Moderator (DAM) ที่ขั้นแรกของการเข้าสลีป6 ระบบเองยังรันเป็นครั้งคราวเพื่อให้เครือข่ายอยู่และรับการแจ้ง แต่คอมโพเนนต์ที่ได้ประโยชน์จากนั้นคือพวกที่เข้าร่วมกลไกนี้ — โค้ดแอปเดสก์ท็อปธรรมดาไม่รัน ดังนั้นจากมุมนักพัฒนา ข้อสรุปเหมือนกันทั้ง Modern Standby และ S3 — ออกแบบบนสมมติฐานว่า โค้ดของคุณไม่รันระหว่างสลีป

ความต่างระหว่างสลีปดั้งเดิมกับ Modern Standbyสลีป S3 ดั้งเดิมหยุดระบบทั้งก้อน ในขณะที่ภายใต้ Modern Standby ระบบยังรันเป็นช่วง ๆ หลังหน้าจอดับ แอปเดสก์ท็อปถูกพักโดย DAM ในทั้งสองกรณี ดังนั้นโค้ดของแอปจึงไม่รันสลีป S3 ดั้งเดิม: ระบบทั้งก้อนหยุดโค้ดของแอปไม่รันModern Standby: ระบบรันเป็นช่วง ๆแอปเดสก์ท็อปถูกพักโดย DAM

ภาพ 4: โมเดลเปลี่ยนแล้ว แต่สำหรับแอปเดสก์ท็อปข้อสรุปเหมือนกัน: “คุณรันระหว่างสลีปไม่ได้”

ข้อควรระวังอีกอย่างคือคุณพึ่งการแจ้งได้น้อยเพียงใด ภายใต้ Modern Standby การเข้าและออกจากโหมดว่างพลังงานต่ำไม่เรียงกับการเปลี่ยนไประงับแบบดั้งเดิม และการเชื่อมต่ออาจขาดไปแล้วโดยไม่มีการแจ้งมาถึงเลย ให้ถือการแจ้งรีซูมเป็นเครื่องช่วย และวางการเชื่อมต่อใหม่ที่ถูกกระตุ้นโดยการตรวจข้อผิดพลาด (บทที่ 5) ไว้บนเส้นทางกู้คืนหลัก

ความต่างอีกอย่างคือความรู้สึก “ลื่น” ของพฤติกรรม การถึงความลึกของสลีปเป็นขั้น ๆ และจังหวะของการตัดการเชื่อมต่อและการหยุดไม่คมเท่าภายใต้ S3 ความต่างระหว่าง “หน้าจอดับเฉย ๆ” กับ “มันสลีป” ผู้ใช้ก็เห็นยากเช่นกัน ดังนั้นเมื่อรับอาการ คุณต้องยืนยันว่า “เขาปิดฝาหรือไม่” และ “ปล่อยว่างกี่นาที”

4. อะไรพัง — อาการคลาสสิก

การเชื่อมต่อ TCP ตาย ระหว่างสลีป อีกฝั่ง NAT และไฟร์วอลล์ถือความเงียบของคุณเป็นหมดเวลาแล้วทิ้งการเชื่อมต่อ ที่แย่กว่า ซ็อกเก็ตฝั่งนี้ไม่รู้เรื่องข้อผิดพลาด ดังนั้นมันล้มเหลวเฉพาะเมื่อคุณส่งหรือรับหลังรีซูม หรือแย่กว่านั้น การรอรับไม่เคยเกิดข้อผิดพลาดเลย (นั่นคือเหตุที่คุณต้องการ keepalive) การเชื่อมต่อฐานข้อมูลและ WebSocket มีรูปเดียวกัน

แฮนเดิลพอร์ตอนุกรมและอุปกรณ์ USB กลายเป็นโมฆะ อุปกรณ์ที่ต่อ USB ตอนรีซูมอาจดูราวกับว่าถูก “ถอดแล้วเสียบกลับ” ครั้งหนึ่ง และแฮนเดิลที่คุณเปิดไว้เริ่มคืนข้อผิดพลาด นั่นคือรูปแบบทั่วไปของแอปควบคุมอุปกรณ์ที่ “ได้ข้อผิดพลาดการสื่อสารเฉพาะหลังอาหารกลางวัน” การออกแบบเชื่อมต่อใหม่ยังอยู่ในบทความเรื่องการสื่อสารอนุกรม

ความต่อเนื่องของเวลาขาด งานที่ขับด้วยตัวตั้งเวลา เช่น “โพลทุก 10 วินาที” ไม่ยิงระหว่างสลีป วิธีที่ยิงทันทีหลังรีซูม (งานที่หมดอายุยิงทันทีครั้งหนึ่ง ไม่เกิดอะไรจนกว่าคาบถัดไป เป็นต้น) ต่างกันตาม timer API และรันไทม์ที่คุณใช้ ดังนั้นอย่าปล่อยการจัดการติ๊กที่พลาดให้พฤติกรรมโดยนัย — แนวทางที่ปลอดภัยคือสร้างตารางใหม่จากการแจ้งรีซูม นอกจากนี้ การคำนวณเวลาที่ผ่านไป (ผลต่างจากตราประทับเวลาก่อนหน้า) กลายเป็น “มูลค่า 8 ชั่วโมง” ทันที และการคำนวณค่าเฉลี่ยหรือการตัดสินหมดเวลาพัง งานตามตาราง เช่น “รันทุกคืนตีสอง” เพียงแค่ไม่รันหากพีซีสลีปอยู่เวลานั้น (ปลุกด้วยฟีเจอร์ปลุกจากสลีปของ Task Scheduler หากต้องการ)

สามรูปที่ความต่อเนื่องของเวลาขาดงานเป็นระยะหยุดระหว่างสลีป และการยิงหลังรีซูมต่างกันตาม API จึงสร้างตารางใหม่ตอนรีซูม ผลต่างจากตราประทับเวลาก่อนหน้ากลายเป็นมหาศาลหลังรีซูม จึงต้องกันไว้ งานตามตารางไม่รันหากเครื่องสลีป จึงพิจารณาการปลุกจากสลีปของ Task Schedulerงานเป็นระยะ: หยุดสร้างตารางใหม่เวลาที่ผ่านไป: พองกันผลต่างผิดปกติตามตาราง: ไม่เคยรันปลุกจากสลีป

ภาพ 5: เขียนการจัดการตัวตั้งเวลาและเวลาบนสมมติฐานว่า “เวลากระโดด” ทั้งสามรูปมีชนิดของมาตรการ

สามสิ่งที่พังข้ามสลีปข้ามสลีป การเชื่อมต่อ TCP ถูกทิ้งโดยหมดเวลาฝั่งตรงข้าม แฮนเดิลอุปกรณ์ USB ถูกทำให้โมฆะเป็นการเชื่อมต่อใหม่ และงานตามเวลาที่ผ่านไปสังเกตการกระโดดของเวลาที่มหาศาล กู้แต่ละอย่างด้วยเชื่อมต่อใหม่ เปิดใหม่ และการกันผลต่างช่วงสลีปTCP: คู่สื่อสารทิ้งไปUSB หรือเวลาที่ผ่านไป?USB: แฮนเดิลโมฆะเวลาที่ผ่านไป: กระโดดตรวจจับ + เชื่อมต่อใหม่เปิดอุปกรณ์ใหม่กันผลต่างผิดปกติ

ภาพ 6: สิ่งที่พังตกเป็นสามตระกูล — “การเชื่อมต่อ” “แฮนเดิล” และ “ความต่อเนื่องของเวลา” — และแต่ละอย่างมีชนิดการกู้คืนที่ลงตัว

การยืนยันตัวตนใหม่ต่อทรัพยากรที่ใช้ร่วม ไดรฟ์เครือข่ายและ VPN มักต้องถูกสร้างใหม่หลังรีซูม และมี “หุบเขาตอนเริ่ม” ไม่กี่วินาทีถึงหลายสิบวินาทีทันทีหลังรีซูมที่การเข้าถึงล้มเหลว จะปลอดภัยกว่าหากไม่ลองทุกอย่างพร้อมกันทันทีหลังรีซูม แต่รอนิดแล้วลองใหม่เป็นขั้น

5. สร้างแอปที่รอดตอนรีซูม

หลักการมีอย่างเดียว สมมติว่า “การเชื่อมต่อและแฮนเดิลไม่รอดข้ามสลีป” และจัดโครงสร้างแอปให้คุณกู้คืนได้เสมอ

ตรวจรีซูมแล้วกู้คืน เมื่อ 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);
}

ทำให้งานเชื่อมต่อใหม่เองidempotent (ปลอดภัยไม่ว่าจะถูกเรียกกี่ครั้ง) ลองใหม่เมื่อล้มเหลวด้วย exponential backoff และในสถานะคงที่ตรวจการเชื่อมต่อที่ตายแต่เนิ่นด้วย keepalive — รวมสามอย่างนั้นเป็นชุด แล้วคุณจะรอดไม่เฉพาะรีซูมจากสลีป แต่ยังรอดการหลุดเครือข่ายสั้น ๆ หรือการรีบูตอุปกรณ์

การออกแบบเชื่อมต่อใหม่ที่ทนรีซูมการแจ้งรีซูม ข้อผิดพลาดการสื่อสาร และความล้มเหลวของ keepalive ทั้งหมดไหลเข้าสู่งานเชื่อมต่อใหม่ idempotent เดียวกัน ซึ่งลองใหม่ด้วย exponential backoff เมื่อล้มเหลวใช่ไม่การแจ้งรีซูม (PBT_APMRESUMEAUTOMATIC)งานเชื่อมต่อใหม่แบบ idempotentการตรวจข้อผิดพลาดการสื่อสารความล้มเหลวของ keepaliveสำเร็จ?กลับสู่การทำงานปกติลองใหม่หลัง exponential backoff

ภาพ 7: รวมการเชื่อมต่อใหม่เข้าสู่เส้นทาง idempotent เส้นเดียว และเข้าถนนเดียวกันจากการแจ้งรีซูม การตรวจข้อผิดพลาด หรือ keepalive

ทบทวนการจัดการเวลา สำหรับงานที่ใช้ “เวลาที่ผ่านไปตั้งแต่ครั้งก่อน” ให้ใส่การกันที่ทำให้ช่วงเวลาเป็นโมฆะเมื่อตรวจพบผลต่างที่ใหญ่ผิดปกติ (อย่าพับเข้าค่าเฉลี่ย อย่าถือเป็นหมดเวลา) การวัดเวลาที่ผ่านไปข้ามรีซูมต้องคงความต่างระหว่างนาฬิกาที่เดินระหว่างสลีป (เวลาตามกำแพง) กับเวลาที่ใช้ทำงานจริง

ยับยั้งสลีปอย่างชัดเจนสำหรับช่วงที่คุณไม่อยากให้สลีปผ่าน ระหว่างงานที่ห้ามถูกสลีปผ่าน — การย้ายข้อมูล การสื่อสารต่อเนื่องกับอุปกรณ์ เป็นต้น — คุณให้ระบบตื่นอยู่ได้ด้วย SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (เพิ่ม ES_DISPLAY_REQUIRED หากอยากให้หน้าจอติดด้วย)45 วิธีที่มีมารยาทกว่าคือ power-request API (PowerCreateRequest + PowerSetRequest) ซึ่งแนบสตริงเหตุผลได้ และ powercfg /requests จะแสดง “ใครกำลังบล็อกและทำไม”8 โปรดทราบว่าการยับยั้งผ่าน SetThreadExecutionState เป็นรายเธรด และคุณยกเลิกจากเธรดเดียวกันที่ตั้งไว้ สำหรับงานที่เปลี่ยนเธรด เช่น async/await ให้ใช้ฝั่ง power request ซึ่งจัดการด้วยแฮนเดิล มีข้อควรระวัง ประการแรก สิ่งที่เหล่านี้ยับยั้งคือสลีปอัตโนมัติเมื่อว่าง พวกมันหยุดการกระทำที่ชัดเจนของผู้ใช้ เช่น ปิดฝาหรือเลือก Sleep จากเมนูเริ่มต้นไม่ได้ ดังนั้นคุณข้ามการออกแบบเชื่อมต่อใหม่ของบทนี้ไม่ได้แม้ขณะกำลังยับยั้ง ประการที่สอง บนแบตเตอรี่บนเครื่อง Modern Standby power request เหล่านี้ยังถูกตัดหลังหมดเวลาสลีปผ่านไปสักพัก งานที่ห้ามถูกขัดจังหวะต้องถูกรับประกันด้วยไฟ AC หรือด้วยการปฏิบัติการ8 ประการที่สาม ยกเลิกเสมอเมื่องานเสร็จ การลืมยกเลิกกลายเป็นบั๊กใหม่: “พีซีเครื่องนี้ ไม่รู้ทำไม ไม่ยอมสลีป”

สองวิธียับยั้งสลีปไม่ว่าคุณจะใช้ SetThreadExecutionState ที่สะดวก หรือ power-request API ที่แนบสตริงเหตุผลได้และผู้ดูแลเห็นผ่าน powercfg ให้ยกเลิกเสมอเมื่องานจบช่วงงานที่ห้ามถูกสลีปผ่านSetThreadExecutionStatePower request (PowerSetRequest)สะดวก — แฟล็กอย่างเดียวมีเหตุผล — เห็นใน powercfgยกเลิกเสมอเมื่องานจบ

ภาพ 8: สำหรับทั้งสองวิธี “ยกเลิกเมื่อเสร็จ” คือเงื่อนไขเด็ดขาด Power request ที่ทำให้เหตุผลมองเห็นได้ใจดีต่อการปฏิบัติการมากกว่า

บริการและแอปที่ไม่มีหน้าต่าง รับการแจ้งแบบคอลแบ็กด้วย RegisterSuspendResumeNotification (DEVICE_NOTIFY_CALLBACK)7 หากการทำงานต่อเนื่องเป็นข้อกำหนดจริง คำตอบรากคือทบทวนการออกแบบที่ให้งานประจำอยู่บนพีซีไคลเอนต์ที่สลีป แล้วย้ายไปฝั่งเซิร์ฟเวอร์หรือเครื่องที่ปฏิบัติการโดยไม่สลีป

6. การสอบสวน — powercfg และล็อกอีเวนต์

การสอบสวนรอบพลังงานใช้เครื่องมือที่มากับ OS ได้ดี

  • มันไม่ยอมสลีป: powercfg /requests แสดงโปรเซสและไดรเวอร์ที่ออก power request “แอปลืมยกเลิก SetThreadExecutionState” ปรากฏที่นี่ด้วย
  • มันตื่นเอง: powercfg /lastwake แสดงเหตุผลตื่นล่าสุด และ powercfg /waketimers แสดงตัวตั้งเวลาที่จองไว้เพื่อปลุกเครื่อง
  • คุณภาพ Modern Standby: powercfg /sleepstudy สร้างรายงานการบริโภคพลังงานและกิจกรรมต่อช่วงสลีป9
  • ยืนยันไทม์ไลน์: แหล่ง Kernel-Power ในล็อกอีเวนต์ (System) เก็บบันทึกการเข้าสลีปและการรีซูม การจับคู่กับล็อกของแอปให้คุณยืนยันอย่างเป็นกลางว่า “มีการรีซูมก่อนข้อผิดพลาดไม่กี่ขณะ”
การแมปอาการปัญหาพลังงานกับคำสั่งสอบสวนสำหรับอาการไม่ยอมสลีป หาว่าใครถือ power request ด้วย powercfg /requests สำหรับอาการตื่นเอง หาเหตุผลตื่นด้วย /lastwake และ /waketimers สำหรับไทม์ไลน์ ใช้ Kernel-Power ในล็อกอีเวนต์มันไม่ยอมสลีปpowercfg /requestsมันตื่นเองpowercfg /lastwake และ /waketimersอยากยืนยันไทม์ไลน์Kernel-Power ในล็อกอีเวนต์การยับยั้งสลีปที่ลืมก็ปรากฏด้วย

ภาพ 9: อาการแมปกับคำสั่งสอบสวนเป็นสามตระกูล ยืนยันก่อนว่า “เพิ่งสลีปหรือไม่” แล้วจึงแยก

ในการจัดการตั๋ว การถามก่อนว่า “พีซีสลีปอยู่ก่อนหน้าเล็กน้อยหรือไม่ (เขาปิดฝาหรือไม่)” เร่งการแยกสาขามาก

7. สรุป

  • สลีปปฏิเสธไม่ได้ การแจ้งล่วงหน้า (PBT_APMSUSPEND) เป็น best-effort พร้อมระยะผ่อนประมาณ 2 วินาที และไม่มาในกรณีฉุกเฉิน วางการออกแบบหลักไว้ฝั่งรีซูม
  • การแจ้งรีซูมคือ PBT_APMRESUMEAUTOMATIC (ตอนรีซูมจากการระงับ) + PBT_APMRESUMESUSPEND (เมื่อผู้ใช้กระทำ) คงการเชื่อมต่อใหม่ที่ขับด้วยข้อผิดพลาดไว้บนเส้นทางหลักสำหรับกรณีที่การแจ้งไม่มาถึง
  • สมมติว่าการเชื่อมต่อและแฮนเดิลไม่รอดข้ามรีซูม และอิมพลีเมนต์ชุดสามชิ้นของการเชื่อมต่อใหม่แบบ idempotent + exponential backoff + keepalive
  • ใส่การกัน “ผลต่างผิดปกติ” บนกิจตามเวลาที่ผ่านไป ออกแบบงานตามตารางบนสมมติฐานว่ามันไม่รันระหว่างสลีป
  • สำหรับช่วงที่ห้ามถูกสลีปผ่าน ให้ยับยั้งสลีปอย่างชัดเจนด้วย SetThreadExecutionState หรือ power request และยกเลิกเสมอเมื่อเสร็จ
  • การสอบสวนคือ powercfg (/requests, /lastwake, /sleepstudy) และล็อกอีเวนต์ Kernel-Power ในการจัดการตั๋ว ให้ถามก่อนว่า “เพิ่งสลีปหรือไม่”

จากมุมของแอป สลีปคืออีเวนต์ที่ “เวลากระโดดโดยไม่มีคำเตือน การเชื่อมต่อไปยังสภาพแวดล้อมถูกตัด แล้วมันกลับมา” ว่าคุณได้ถักสิ่งนั้นเข้าไปในการออกแบบเป็นส่วนหนึ่งของชีวิตประจำวัน แทนที่จะเป็นสถานการณ์ผิดปกติ คือสิ่งที่แยกความเสถียรของแอปธุรกิจในยุคแล็ปท็อป

บทความที่เกี่ยวข้อง

ด้านให้คำปรึกษาที่เกี่ยวข้อง

KomuraSoft LLC รับการสอบสวนหาสาเหตุของบั๊ก เช่น “การสื่อสารขาดหลังรีซูมจากสลีป” และ “การเชื่อมต่อกับอุปกรณ์หลุดหลังอาหารกลางวัน” การต่อตรรกะเชื่อมต่อใหม่และการจัดการอีเวนต์พลังงานเข้ากับแอปที่มีอยู่ และการตรวจทานการออกแบบของแอปธุรกิจและซอฟต์แวร์ควบคุมอุปกรณ์ที่สมมติการปฏิบัติการบนแล็ปท็อป

ลิงก์อ้างอิง

  1. Microsoft Learn, PBT_APMSUSPEND event. ว่าด้วยนี่คืออีเวนต์ที่มาถึงก่อนคอมพิวเตอร์เข้าสู่สถานะระงับไม่กี่ขณะ ว่าด้วยแอปถูกคาดหวังให้ทำงานที่จำเป็นเพื่อบันทึกข้อมูลให้เสร็จ และว่าด้วยระบบอนุญาตประมาณ 2 วินาทีเพื่อจัดการการแจ้งนี้ โดยแอปที่ทำต่อเกินนั้นอาจถูกขัดจังหวะ  2

  2. Microsoft Learn, System Power Management Events. ว่าด้วยระบบกระจายการเปลี่ยนโหมดการทำงาน เช่น สลีป ล่วงหน้า ว่าด้วย PBT_APMSUSPEND ถูกแจ้งก่อนสลีปเมื่อว่างเพื่อให้คุณเตรียมด้วยการปิดไฟล์และบันทึกข้อมูล ว่าด้วยการระงับฉุกเฉิน (แบตเตอรี่วิกฤต เป็นต้น) ไม่ให้การแจ้งล่วงหน้า ว่าด้วยการจัดการข้อความนี้ได้รับอนุญาตสูงสุด 2 วินาทีต่อแอปและถูกตัดหลังหมดเวลา และว่าด้วยทุกแอปถูกแจ้งตอนรีซูม  2 3

  3. Microsoft Learn, PBT_APMRESUMESUSPEND event. ว่าด้วยสิ่งนี้ถูกส่งหลัง PBT_APMRESUMEAUTOMATIC เมื่อรีซูมที่ผู้ใช้เป็นผู้เริ่มหรือเมื่อตรวจพบอินพุตของผู้ใช้ในภายหลัง ว่าด้วยมีเพียง PBT_APMRESUMEAUTOMATIC ถูกส่งสำหรับการรีซูมจากสาเหตุภายนอก เช่น การปลุกจากระยะไกล และว่าด้วยแอปถูกคาดหวังให้เปิดไฟล์ที่ปิดตอนสลีปใหม่และเตรียมสำหรับอินพุตของผู้ใช้  2

  4. Microsoft Learn, WM_POWERBROADCAST message. ว่าด้วย PBT_APMRESUMEAUTOMATIC ถูกส่งเสมอตอนรีซูม โดย PBT_APMRESUMESUSPEND ถูกส่งด้วยเมื่อรีซูมจากอินพุตของผู้ใช้ ว่าด้วยข้อความนี้ไม่แยกชนิดของสถานะพลังงานต่ำ ว่าด้วยรายละเอียดของการเปลี่ยนสถานะพลังงานถูกบันทึกในล็อกอีเวนต์ของระบบ และว่าด้วยการเรียก SetThreadExecutionState เพื่อกันระบบไม่ให้เข้าสู่สถานะพลังงานต่ำ  2 3 4

  5. Microsoft Learn, SetThreadExecutionState function (winbase.h). ว่าด้วย ES_SYSTEM_REQUIRED และ ES_DISPLAY_REQUIRED สามารถยับยั้งสลีปเมื่อว่างของระบบและการปิดพลังงานจอได้ และว่าด้วยการประกาศการยับยั้งต่อเนื่องด้วย ES_CONTINUOUS และการยกเลิกด้วยการเรียก ES_CONTINUOUS อย่างเดียวเมื่อเสร็จ  2

  6. Microsoft Learn, Prepare software for modern standby. ว่าด้วย Desktop Activity Moderator (DAM) พักแอปเดสก์ท็อปที่ขั้นแรกของการเปลี่ยนเข้าสู่ Modern Standby และว่าด้วยระบบจากนั้นเคลื่อนเป็นขั้นเข้าสู่เฟสพลังงานต่ำและเฟสความยืดหยุ่น โดยมีเพียงคอมโพเนนต์ที่อนุญาตที่รันเป็นช่วง ๆ  2

  7. Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). ว่าด้วยนี่คือ API ที่ลงทะเบียนเพื่อรับการแจ้งระงับ/รีซูม และว่าด้วยการระบุ DEVICE_NOTIFY_CALLBACK เพื่อให้แอปหรือบริการที่ไม่มีหน้าต่างรับการแจ้งผ่านคอลแบ็กได้ นอกจากการส่งข้อความไปยังแฮนเดิลหน้าต่าง  2

  8. Microsoft Learn, PowerSetRequest function (winbase.h). ว่าด้วยสามารถตั้งชนิดคำขอ เช่น ให้ระบบหรือจอตื่นอยู่ บนออบเจ็กต์ power request ที่สร้างด้วย PowerCreateRequest ว่าด้วยสามารถแนบสตริงเหตุผลสำหรับวินิจฉัยได้ และว่าด้วย power request ที่ค้างสามารถแจกแจงด้วย powercfg /requests  2

  9. Microsoft Learn, Modern standby SleepStudy. ว่าด้วยรายงานที่สร้างโดย powercfg /sleepstudy ให้คุณตรวจ ต่อช่วง Modern Standby การบริโภคพลังงาน กิจกรรม และเหตุผลตื่น (ปุ่มเปิดปิด อินพุตของผู้ใช้ ตัวตั้งเวลาปลุก เป็นต้น) 

บทความล่าสุดที่มีแท็กเดียวกัน เพื่อเจาะลึกหัวข้อใกล้เคียง

DllMain และ Loader Lock — เหตุผลจริงที่ถูกบอกให้ "อย่าทำอะไรในการเริ่มต้น DLL"

ทำไมคุณต้องไม่เรียก LoadLibrary หรือซิงโครไนซ์กับเธรดอื่นจาก DllMain บทความนี้อธิบายจากแหล่งปฐมภูมิว่า loader lock ทำให้การแจ้ง DLL ทุกคร...

"ไม่ตอบสนอง" คืออะไรจริง ๆ — Windows ตัดสินว่าแอปแฮงอย่างไร และออกแบบอย่างไรให้ไม่แฮง

"ไม่ตอบสนอง" ของ Windows คือกลไกที่ OS ตัดสินว่าหน้าต่างไม่ได้ดึงข้อความเป็นเวลา 5 วินาทีแล้วแทนที่ด้วยหน้าต่างโกสต์ บทความนี้ครอบคลุมภาย...

Win32 Thread Pool API — คอนเคอร์เรนซีโดยไม่สร้างเธรดเอง ด้วย CreateThreadpoolWork

โค้ดเนทีฟของคุณกระจายการเรียก CreateThread ไปทั่วหรือไม่ บทความนี้อธิบาย Win32 thread pool API ที่ออกแบบใหม่ใน Vista — ออบเจ็กต์สี่ชนิด w...

Named Pipe ในทางปฏิบัติ — IPC มาตรฐานของ Windows ตั้งแต่การออกแบบถึงความปลอดภัย

คู่มือเชิงปฏิบัติเรื่อง named pipe ซึ่งเป็นการสื่อสารระหว่างโปรเซสมาตรฐานของ Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิ ทั้งการเลือกระหว่...

Spurious Wakeup — ทำไม condition variable จึงตื่น "โดยไม่ถูกแจ้ง" และวิธีรออย่างถูกต้องบน Windows

การรอของ condition variable สามารถกลับได้แม้ไม่มีการแจ้งมาถึง (spurious wakeup) บทความนี้อธิบายจากการอิมพลีเมนต์ของ Windows ว่าทำไมข้อกำห...

หน้าเหล่านี้วางหัวข้อไว้ในบริบทที่กว้างขึ้นของบริการและการตัดสินใจ

บทความนี้เกี่ยวข้องโดยตรงกับบริการต่อไปนี้

คำถามที่พบบ่อย

คำถามที่มักพบในการปรึกษาเกี่ยวกับหัวข้อของบทความนี้

แอปเรียนรู้เรื่องสลีปล่วงหน้าแล้วปฏิเสธได้หรือไม่
บน Windows ปัจจุบันคุณรับการแจ้งได้ แต่ปฏิเสธไม่ได้ ก่อนสลีปไม่กี่ขณะ ข้อความ WM_POWERBROADCAST ส่งอีเวนต์ PBT_APMSUSPEND และที่นี่คุณเตรียมได้ด้วยการปิดไฟล์และบันทึกสถานะ แต่เวลาที่อนุญาตให้ประมวลผลคือประมาณ 2 วินาทีต่อแอป และหากเกินระบบจะเดินต่อโดยไม่รอ ในการระงับฉุกเฉิน เช่น แบตเตอรี่ต่ำวิกฤต การแจ้งล่วงหน้าเองก็ไม่มา การออกแบบที่ "ต้องทำให้เสร็จก่อนสลีป" จึงไม่ตั้งอยู่ได้ คุณต้องการการออกแบบที่กู้คืนตอนรีซูมได้ไม่ว่าจะถูกตัดเมื่อใด สำหรับช่วงงานที่คุณไม่อยากให้สลีปผ่านจริง ๆ ให้ยับยั้งสลีปอย่างชัดเจนด้วย SetThreadExecutionState หรือ power request (PowerSetRequest)
จะตรวจได้อย่างไรว่าเครื่องรีซูมแล้ว
หากแอปมีหน้าต่าง ให้จัดการ WM_POWERBROADCAST เมื่อรีซูมจากการระงับ PBT_APMRESUMEAUTOMATIC จะมาถึง และหากการรีซูมเกิดจากการกระทำของผู้ใช้ (ปุ่มเปิดปิดหรือการกดแป้น) PBT_APMRESUMESUSPEND จะตามมา การรีซูมแบบไม่มีผู้ดูแลที่กลับไปสลีปทันทีส่งเฉพาะ PBT_APMRESUMEAUTOMATIC ดังนั้นการแยกพื้นฐานคือวางงานที่จำเป็น เช่น เชื่อมต่อใหม่ ไว้ฝั่ง PBT_APMRESUMEAUTOMATIC และงานที่หันหน้าหาผู้ใช้ เช่น อัปเดตหน้าจอ ไว้ฝั่ง PBT_APMRESUMESUSPEND บริการที่ไม่มีหน้าต่างและแอปคอนโซลรับการแจ้งเดียวกันผ่านคอลแบ็กได้ด้วย RegisterSuspendResumeNotification คู่กับ DEVICE_NOTIFY_CALLBACK
ฉันให้แอปรันต่อไประหว่างสลีปได้หรือไม่
ตามกฎแล้วไม่ได้ ระหว่างสลีป การรันของ CPU เองหยุด (บนเครื่อง Modern Standby แอปเดสก์ท็อปถูกพักโดย Desktop Activity Moderator) และโค้ดของแอปไม่รัน มีสองทางเลือก หนึ่งคือยับยั้งสลีปเฉพาะขณะงานกำลังดำเนิน การระบุ ES_SYSTEM_REQUIRED กับ SetThreadExecutionState หรือออก power request ด้วย PowerCreateRequest/PowerSetRequest จะยับยั้งสลีปอัตโนมัติเมื่อว่างในช่วงนั้น (ยืนยันได้ด้วย powercfg /requests) นั่นยังหยุดการสลีปที่ชัดเจน เช่น ผู้ใช้ปิดฝาไม่ได้ ดังนั้นคุณยังต้องพร้อมสำหรับรีซูมแม้ขณะกำลังยับยั้ง อีกทางคือยอมรับสลีปแล้วออกแบบให้ "ตามให้ทันหลังรีซูม" สำหรับงานตามตาราง เช่น แบตช์กลางคืน คุณยังปลุกพีซีด้วย "ปลุกคอมพิวเตอร์เพื่อรันงานนี้" ของ Task Scheduler ได้ งานที่ต้องรันต่อเนื่องจริง ๆ อยู่ในเซิร์ฟเวอร์หรือบริการที่ตั้งค่าไม่ให้สลีป
ทำไมการเชื่อมต่อ TCP และพอร์ตอนุกรมจึงหยุดทำงานหลังรีซูม
เพราะอะแดปเตอร์เครือข่ายและอุปกรณ์ USB ก็ลงสู่สถานะพลังงานต่ำระหว่างสลีปเช่นกัน การเชื่อมต่อ TCP ถูกทิ้งไปแล้วโดยอีกฝั่งหรือโดยหมดเวลาของ NAT หรือไฟร์วอลล์ และการส่ง/รับหลังรีซูมคืนข้อผิดพลาด (คุณมักไม่รู้จนกว่าจะเกิดข้อผิดพลาด) อะแดปเตอร์ USB-to-serial และคล้ายกันบางครั้งถูกจัดการเป็นการถอดและเสียบอุปกรณ์ใหม่ตอนรีซูม และแฮนเดิลที่คุณเปิดไว้กลายเป็นโมฆะ สำหรับทั้งคู่ สมมติฐานที่ถูกต้องคือ "แฮนเดิลและการเชื่อมต่อไม่รอดข้ามรีซูม" และคำตอบที่ถูกคืออิมพลีเมนต์ตรรกะเชื่อมต่อใหม่ที่สร้างการเชื่อมต่อใหม่จากการแจ้งรีซูมหรือข้อผิดพลาดการสื่อสาร การรวม keepalive เป็นระยะกับการลองใหม่ที่ใช้ exponential backoff เมื่อล้มเหลวคือรูปแบบที่ลงตัว
จะสอบสวนสลีปที่ไม่คาดคิดหรือรีซูมที่ไม่คาดคิดอย่างไร
คำสั่ง powercfg คือเครื่องมือแรก ในทิศทาง "มันไม่ยอมสลีป" powercfg /requests แสดงโปรเซสและไดรเวอร์ใดออก power request ที่กำลังบล็อกสลีป ในทิศทาง "มันตื่นเอง" powercfg /lastwake แสดงเหตุผลตื่นล่าสุด และ powercfg /waketimers แสดงตัวตั้งเวลาที่จองไว้เพื่อปลุกเครื่อง บนเครื่อง Modern Standby powercfg /sleepstudy สร้างรายงานการบริโภคและกิจกรรมระหว่างสลีป ประวัติสลีปและรีซูมยังถูกบันทึกในล็อกอีเวนต์ (แหล่ง Kernel-Power ในล็อก System) ดังนั้นคุณยืนยันบนไทม์ไลน์ได้ว่า "เมื่อใดมันสลีป และเมื่อใดและทำไมมันจึงตื่น"

โปรไฟล์ผู้เขียน

หน้าแนะนำผู้เขียนบทความ

Go Komura

ผู้แทนของ KomuraSoft LLC

เชี่ยวชาญด้านการพัฒนาซอฟต์แวร์ Windows ที่ปรึกษาเทคนิค และการตรวจสอบบั๊ก โดยเฉพาะโปรเจกต์ที่มีระบบเดิมและบั๊กที่ทำซ้ำได้ยาก

ลิงก์สาธารณะ

กลับไปยังบล็อก